Создание полностековых постоянных приложений с помощью Arweave, Smartweave и Next.js


Код для этого урока можно найти здесь

В этом уроке вы узнаете, как создавать полностековые dapps на Arweave с помощью Smarweave, Warp и Next.js.

Краткое описание Smartweave

  • Создавайте смарт-контракты на JS, TS или Rust
  • Выполнять произвольные объемы вычислений без дополнительной платы
  • Никогда не нужно беспокоиться об оптимизации газа
  • Никакого раздувания состояния
  • Может напрямую обрабатывать богатый контент / большие файлы
  • Warp предлагает улучшения (скорость, кэширование, sdks)

Приложение, которое мы будем строить, — это блог полного стека, что означает, что у вас будет открытый, публичный и композитный бэк-энд, который можно передавать и повторно использовать где угодно (не только в этом приложении).

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

Я думаю, что это хороший пример, поскольку он не слишком простой, чтобы быть скучным, но и не слишком сложный, чтобы быть запутанным. Он показывает, как сделать большинство основных вещей, которые вам понадобятся и которые вы захотите понять для создания более сложных и изощренных приложений в будущем.

Об Arweave

Arweave — это протокол web3, который позволяет разработчикам постоянно хранить файлы, такие как изображения, видео и pdf, а также одностраничные веб-приложения.

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

Smartweave

Arweave также представила SmartWeave: протокол смарт-контрактов, который позволяет разработчикам создавать постоянные приложения на базе Arweave.

Когда вы публикуете контракт Smartweave, исходный код программы и ее начальное состояние хранятся в транзакции Arweave.

Когда пользователь пишет обновление для программы SmartWeave, он записывает свои данные в виде новой транзакции Arweave.

Для вычисления состояния контракта клиент SmartWeave использует исходный код контракта для последовательного выполнения истории входов. Недействительные транзакции игнорируются.

Таким образом, SmartWeave перекладывает ответственность за проверку транзакций на пользователей.

Warp

Warp (https://warp.cc/) — это протокол, построенный поверх Arweave и предназначенный для улучшения DX/UX при разработке приложений Smartweave.

Warp состоит из 3 основных слоев:

  1. Слой основного протокола — это реализация оригинального протокола SmartWeave, который отвечает за связь со смарт-контрактами SmartWeave, развернутыми на Arweave.

  2. Слой Caching — построен поверх слоя Core Protocol и позволяет кэшировать результаты каждого из модулей Core Protocol отдельно.

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

  3. Уровень расширений — CLI, средства отладки, различные реализации протоколирования, так называемые «сухие прогоны» (т.е. действия, позволяющие быстро проверить результат взаимодействия с контрактом, ничего не записывая в Arweave).

Начало работы

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

Предварительные условия

Для успешного прохождения этого руководства на вашей машине должен быть установлен Node.js 16.17.0 или выше.

Я рекомендую использовать nvm или fnm для управления версиями Node.js.

Создание и настройка проекта

Чтобы начать работу, давайте сначала создадим приложение Next.js, настроим его и установим зависимости.

npx create-next-app full-stack-arweave
Вход в полноэкранный режим Выйдите из полноэкранного режима

Перейдите в новый каталог и установите следующие зависимости:

npm install warp-contracts react-markdown uuid
Войти в полноэкранный режим Выход из полноэкранного режима

Настройка приложения Next.js

Откройте package.json и добавьте следующую конфигурацию:

"type": "module",
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем обновите next.config.js, чтобы использовать ES Modules для экспорта nextConfig:

/* replace */
module.exports = nextConfig

/* with this*/
export default nextConfig
Вход в полноэкранный режим Выйти из полноэкранного режима

Это позволит приложению Next.js использовать ES-модули.

Далее добавьте следующее в файл .gitignore:

wallet.json
testwallet.json
transactionid.js
Войти в полноэкранный режим Выйти из полноэкранного режима

Никогда не публикуйте информацию о кошельке в таких публичных местах, как GitHub. В этом руководстве мы будем работать только с testnet, но у нас будет код, который вы сможете опубликовать в mainnet. На всякий случай, мы добавляем wallet.json в .gitignore.

Контракты искривления

Далее давайте создадим и протестируем смарт-контракты.

О контрактах Smartweave

Контракты Smartweave работают следующим образом.

1. Начальное состояние для приложения определяется как объект JSON.

Базовое начальное состояние может выглядеть следующим образом для приложения счетчика, который увеличивает и уменьшает число:

{
"counter" : 0
}
Вход в полноэкранный режим Выход из полноэкранного режима

2. Логика контракта Smartweave записывается в функции handle.

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

Базовый обработчик для счетчика, использующего вышеуказанное состояние, может выглядеть следующим образом:

export function handle(state, action) {
  if (action.input.function === 'increment') {
    state.counter += 1
  }
  if (action.input.function === 'decrement') {
    state.counter -= 1
  }
  return { state }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

В этом обработчике есть два действия — increment или decrement. Логика здесь довольно проста.

3. Чтобы обновить состояние контракта, мы можем вызвать writeInteraction из Warp SDK.

Вот базовый пример того, как это может выглядеть при вызове этой функции на сервере:

import { WarpFactory } from 'warp-contracts'
const transactionId = "BA3EIfkKvlPXLk5sEN8loAmp2zr0MezSPhwaujTNli8"
import wallet from './wallet.json'

let warp = WarpFactory.forLocal()
const contract = warp.contract(transactionId).connect(wallet)
await contract.writeInteraction({
  function: "decrement"
})
Вход в полноэкранный режим Выйти из полноэкранного режима

Затем мы можем прочитать состояние в любое время:

const contract = warp.contract(transactionId).connect();
const { cachedValue } = await contract.readState();
Войти в полноэкранный режим Выйти из полноэкранного режима

Написание контракта

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

В корне проекта создайте новую папку с именем warp.

В этой папке создайте новый файл contract.js:

/* warp/contract.js */
export function handle(state, action) {
  /* address of the caller is available in action.caller */
  if (action.input.function === 'initialize') {
    state.author = action.caller
  }
  if (action.input.function === 'createPost' && action.caller === state.author) {
    const posts = state.posts
    posts[action.input.post.id] = action.input.post
    state.posts = posts
  }
  if (action.input.function === 'updatePost' && action.caller === state.author) {
    const posts = state.posts
    const postToUpdate = action.input.post
    posts[postToUpdate.id] = postToUpdate
    state.posts = posts
  }
  if (action.input.function === 'deletePost' && action.caller === state.author) {
    const posts = state.posts
    delete posts[action.input.post.id]
    state.posts = posts
  }
  return { state }
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Это контракт для нашего приложения для ведения блога.

У нас есть функции для создания, обновления и удаления записи (CRUD). У нас также есть функция initialize, которая добавляет базовое правило авторизации, позволяющее только владельцу блога вызывать любую из этих функций, указав в качестве владельца разработчика контракта.

Далее создайте файл в каталоге warp с именем state.json и добавьте следующий JSON:

{
  "posts": {},
  "author": null
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Здесь у нас есть начальное состояние posts, установленное на пустой объект, а author установлен на null.

Мы закончили с нашим контрактом, теперь давайте напишем код для развертывания, обновления и чтения состояния контракта.

Развертывание, обновление и чтение

Далее создайте новый файл configureWarpServer.js в каталоге warp.

import { WarpFactory } from 'warp-contracts'
import fs from 'fs'

/*
*  environment can be 'local' | 'testnet' | 'mainnet' | 'custom';
*/

const environment = process.env.WARPENV || 'testnet'
let warp

if (environment === 'testnet') {
  warp = WarpFactory.forTestnet()
} else if (environment === 'mainnet') {
  warp = WarpFactory.forMainnet()
} else {
  throw Error('environment not set properly...')
}

async function configureWallet() {
  try {
    if (environment === 'testnet') {
      /* for testing, generate a temporary wallet */
      try {
        return JSON.parse(fs.readFileSync('../testwallet.json', 'utf-8'))
      } catch (err) {
        const { jwk } = await warp.testing.generateWallet()
        fs.writeFileSync('../testwallet.json', JSON.stringify(jwk))
        return jwk
      }
    } else if (environment === 'mainnet') {
      /* for mainnet, retrieve a local wallet */
      return JSON.parse(fs.readFileSync('../wallet.json', 'utf-8'))
    } else {
      throw Error('Wallet not configured properly...')
    }
  } catch (err) {
    throw Error('Wallet not configured properly...', err)
  }
}

export {
  configureWallet,
  warp
}
Войдите в полноэкранный режим Выйти из полноэкранного режима

В этом файле мы настраиваем сервер warp в зависимости от того, находимся ли мы в среде testing или mainnet (production).

Затем у нас есть функция, которая настраивает кошелек, который мы будем использовать для развертывания контракта. Если мы тестируем, мы можем просто создать тестовый кошелек автоматически с помощью generateWallet. Если мы находимся в производстве, у нас есть возможность импортировать кошелек локально.

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

Развертывание контракта

Создайте новый файл с именем deploy.js в каталоге warp со следующим кодом:

import fs from 'fs'
import { configureWallet, warp } from './configureWarpServer.js'

async function deploy() {
  const wallet = await configureWallet()
  const state = fs.readFileSync('state.json', 'utf-8')
  const contractsource = fs.readFileSync('contract.js', 'utf-8')

  const { contractTxId } = await warp.createContract.deploy({
    wallet,
    initState: state,
    src: contractsource
  })
  fs.writeFileSync('../transactionid.js', `export const transactionId = "${contractTxId}"`)

  const contract = warp.contract(contractTxId).connect(wallet)
  await contract.writeInteraction({
    function: 'initialize'
  })
  const { cachedValue } = await contract.readState()

  console.log('Contract state: ', cachedValue)
  console.log('contractTxId: ', contractTxId)
}

deploy()
Войти в полноэкранный режим Выйти из полноэкранного режима

Функция deploy развернет контракт в Arweave и запишет идентификатор транзакции в локальную файловую систему.

Чтение состояния

Далее создадим файл с именем read.js со следующим кодом:

import { warp } from './configureWarpServer.js'
import { transactionId } from '../transactionid.js'

async function read() {
  const contract = warp.contract(transactionId).connect();
  const { cachedValue } = await contract.readState();

  console.log('Contract state: ', JSON.stringify(cachedValue))
}
read()
Вход в полноэкранный режим Выход из полноэкранного режима

Написание обновления

Последняя функция, которую мы напишем, предназначена для создания нового поста.

В директории warp создайте новый файл createPost.js со следующим кодом:

import { warp, configureWallet } from './configureWarpServer.js'
import { transactionId } from '../transactionid.js'
import { v4 as uuid } from 'uuid'

async function createPost() {
  let wallet = await configureWallet()
  const contract = warp.contract(transactionId).connect(wallet)
  await contract.writeInteraction({
    function: "createPost",
    post: {
      title: "Hi from first post!",
      content: "This is my first post!",
      id: uuid()
    }
  })
}

createPost()
Вход в полноэкранный режим Выйти из полноэкранного режима

Тестирование

Теперь мы можем все протестировать.

Чтобы развернуть контракт, выполните следующую команду из каталога warp:

node deploy
Войти в полноэкранный режим Выйти из полноэкранного режима

Это приведет к развертыванию контракта в testnet.

После развертывания контракта вы можете использовать проводник блоков Sonar для просмотра контракта и его текущего состояния. ID транзакции контракта будет доступен в transactionid.js. Обязательно переключитесь на testnet, чтобы просмотреть контракт из этого развертывания.

Далее, давайте прочитаем текущее состояние:

node read
Войти в полноэкранный режим Выйти из полноэкранного режима

Возвращенное состояние контракта должно выглядеть примерно так:

{"state":{"posts":{},"author":"-YzqAM_VDCqFZEk6iZ3B8Y-b6SxHoh0F1SvjOCW49nY"},"validity":{"36CmMGSlrGNvvCCfldtiUza4ZnQ9_bFW0YoEh8NCVe0":true},"errorMessages":{}}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь давайте создадим пост:

node createPost
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь, когда мы читаем обновленное состояние, мы должны увидеть новый пост в обновленном состоянии:

node read
Войти в полноэкранный режим Выйти из полноэкранного режима

Создание веб-приложения

Теперь, когда мы поняли, как развернуть и протестировать контракт Smartweave с помощью Warp, давайте создадим внешнее приложение, которое будет взаимодействовать с ним и использовать его.

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

  1. Представление для просмотра постов, созданных пользователем.
  2. Представление, позволяющее пользователям создавать посты.

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

Создайте новый файл с именем configureWarpClient.js в корне приложения и добавьте следующий код:

import { WarpFactory } from 'warp-contracts'
import { transactionId } from './transactionid'
import wallet from './testwallet'

/*
*  environment can be 'local' | 'testnet' | 'mainnet' | 'custom';
*/

const environment = process.env.NEXT_PUBLIC_WARPENV || 'testnet'
let warp
let contract

async function getContract() {
  if (environment == 'testnet') {
    warp = WarpFactory.forTestnet()
    contract = warp.contract(transactionId).connect(wallet)
  } else if (environment === 'mainnet') {
    warp = WarpFactory.forMainnet()
    contract = warp.contract(transactionId).connect()
  } else {
    throw new Error('Environment configured improperly...')
  }
  return contract
}

export {
  getContract
}
Вход в полноэкранный режим Выход из полноэкранного режима

Создание поста

Далее в директории pages создайте новый файл create-post.js и добавьте следующий код:

import { useState } from 'react'
import { getContract } from '../configureWarpClient'
import { v4 as uuid } from 'uuid'
import { useRouter } from 'next/router'

export default function createPostComponent() {
  const [post, updatePost] = useState({
    title: '', content: ''
  })
  const router = useRouter()

  async function createPost() {
    if (!post.title || !post.content) return
    post.id = uuid()
    const contract = await getContract()
    try {
      const result = await contract.writeInteraction({
        function: "createPost",
        post
      })
      console.log('result:', result)
      router.push('/')
    } catch (err) {
      console.log('error:', err)
    }
  }
  return (
    <div style={formContainerStyle}>
      <input
        value={post.title}
        placeholder="Post title"
        onChange={e => updatePost({ ...post, title: e.target.value})}
        style={inputStyle}
      />
      <textarea
        value={post.content}
        placeholder="Post content"
        onChange={e => updatePost({ ...post, content: e.target.value})}
        style={textAreaStyle}
      />
      <button style={buttonStyle} onClick={createPost}>Create Post</button>
    </div>
  )
}

const formContainerStyle = {
  width: '900px',
  margin: '0 auto',
  display: 'flex',
  flexDirection: 'column',
  alignItems: 'flex-start'
}

const inputStyle = {
  width: '300px',
  padding: '8px',
  fontSize: '18px',
  border: 'none',
  outline: 'none',
  marginBottom: '20px'
}

const buttonStyle = {
  width: '200px',
  padding: '10px 0px'
}

const textAreaStyle = {
  width: '100%',
  height: '300px',
  marginBottom: '20px',
  padding: '20px'
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Чтение и отображение постов

Затем обновите pages/index.js со следующим кодом:

import { useEffect, useState } from 'react'
import { getContract } from '../configureWarpClient'
import ReactMarkdown from 'react-markdown'

export default function Home() {
  const [posts, setPosts] = useState([])
  useEffect(() => {
    readState()
  }, [])
  async function readState() {
    const contract = await getContract()
    try {
      const data = await contract.readState()
      console.log('data: ', data)
      const posts = Object.values(data.cachedValue.state.posts)
      setPosts(posts)
      console.log('posts: ', posts)
    } catch (err) {
      console.log('error: ', err)
    }
  }

  return (
    <div style={containerStyle}>
      <h1 style={headingStyle}>PermaBlog</h1>
      {
        posts.map((post, index) => (
          <div key={index} style={postStyle}>
            <p style={titleStyle}>{post.title}</p>
            <ReactMarkdown>
              {post.content}
            </ReactMarkdown>
          </div>
        ))
      }
    </div>
  )
}

const containerStyle = {
  width: '900px',
  margin: '0 auto'
}

const headingStyle = {
  fontSize: '64px'
}
const postStyle = {
  padding: '15px 0px 0px',
  borderBottom: '1px solid rgba(255, 255, 255, .2)'
}

const titleStyle = {
  fontSize: '34px',
  marginBottom: '0px'
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Далее, обновите pages/_app.js.

import '../styles/globals.css'
import Link from 'next/link'

function MyApp({ Component, pageProps }) {
  return (
    <div>
      <nav style={navStyle}>
        <Link href="/">
          <a style={linkStyle}>
            Home
          </a>
        </Link>
        <Link href="/create-post" >
          <a style={linkStyle}>
            Create Post
          </a>
        </Link>
      </nav>
      <Component {...pageProps} />
    </div>
  )
}

const navStyle = {
  padding: '30px 100px'
}

const linkStyle = {
  marginRight: '30px'
}

export default MyApp
Войти в полноэкранный режим Выход из полноэкранного режима

Тестирование

Теперь давайте запустим приложение и протестируем его:

npm run dev
Войти в полноэкранный режим Выход из полноэкранного режима

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

Далее создайте пост. Если пост успешно создан, он должен появиться в списке постов на главной странице.

Следующие шаги

Развертывание в mainnet

Если вы хотите развернуть и подключиться к сети Arweave mainnet, выполните следующие шаги:

  1. Скачайте кошелек ArConnect

  2. Запросите AR-токены из крана, купите их на бирже или обменяйте на бирже типа changeNOW.

  3. Загрузите новый кошелек в файл с именем wallet.json. Обязательно добавьте этот файл в .gitignore и никогда не публикуйте его и не публикуйте в Git.

  4. Установите переменную локального окружения mainnet в терминальной сессии, из которой будет выполняться развертывание:

    export WARPENV=mainnet
    
  5. Создайте файл .env.local в корне приложения и добавьте следующую переменную окружения:

    NEXT_PUBLIC_WARPENV=mainnet
    
  6. Разверните контракт из каталога warp:

    node deploy
    
  7. Запустите приложение

    npm run dev
    

Учебные ресурсы

Если вы хотите углубиться и узнать больше о Warp, Smartweave и Arweave, загляните в Академию Warp.

Оцените статью
devanswers.ru
Добавить комментарий