diff --git a/public/content/translations/ru/developers/docs/transactions/index.md b/public/content/translations/ru/developers/docs/transactions/index.md
index 21303ded0b6..021e638fed3 100644
--- a/public/content/translations/ru/developers/docs/transactions/index.md
+++ b/public/content/translations/ru/developers/docs/transactions/index.md
@@ -1,20 +1,21 @@
---
-title: Транзакции
-description: 'Обзор транзакций Ethereum: как они работают, их структура данных и как их отправлять через приложение.'
+title: "Транзакции"
+description: "Обзор транзакций Ethereum: как они работают, их структура данных и как их отправлять через приложение."
lang: ru
---
Транзакции — это криптографически подписанные инструкции от аккаунтов. Аккаунт инициирует транзакцию для обновления состояния сети Ethereum. Самая простая транзакция — перевод ETH с одного аккаунта на другой.
-## Прежде чем начать {#prerequisites}
+## Предварительные условия {#prerequisites}
-Чтобы помочь вам лучше понять эту страницу, мы рекомендуем сначала прочитать разделы [Аккаунты](/developers/docs/accounts/) и наше [Введение в Ethereum](/developers/docs/intro-to-ethereum/).
+Чтобы помочь вам лучше понять эту страницу, мы рекомендуем сначала прочитать [Аккаунты](/developers/docs/accounts/) и наше [введение в Ethereum](/developers/docs/intro-to-ethereum/).
## Что такое транзакция? {#whats-a-transaction}
Транзакция Ethereum относится к действию, инициированному внешним аккаунтом, то есть аккаунтом, управляемым человеком, а не контрактом. Например, если Боб отправляет Алисе 1 ETH, аккаунт Боба должен быть дебетован, а счет Алисы — кредитован. Это действие по изменению состояния происходит внутри транзакции.
- _Источник адаптированной диаграммы: [Ethereum EVM illustrated](https://takenobu-hs.github.io/downloads/ethereum_evm_illustrated.pdf)_
+
+_Диаграмма адаптирована из [Ethereum EVM illustrated](https://takenobu-hs.github.io/downloads/ethereum_evm_illustrated.pdf)_
Транзакции, которые изменяют состояние EVM, должны транслироваться по всей сети. Любой узел может транслировать запрос на выполнение транзакции на EVM; после этого валидатор выполнит транзакцию и распространит результирующее измененное состояние на остальную часть сети.
@@ -22,17 +23,17 @@ lang: ru
Отправленная транзакция включает следующую информацию:
-- `from` - адрес отправителя, который будет подписывать транзакцию. Это будет внешний аккаунт, поскольку аккаунты контрактов не могут отправлять транзакции.
-- `to` — адрес получателя (если аккаунт внешний, транзакция передаст информацию о стоимости. Если это аккаунт контракта, транзакция выполнит код контракта).
+- `from` — адрес отправителя, который будет подписывать транзакцию. Это будет внешний аккаунт, поскольку аккаунты контрактов не могут отправлять транзакции
+- `to` — адрес получателя (если это внешний аккаунт, транзакция передаст стоимость. Если это аккаунт контракта, транзакция выполнит код контракта).
- `signature` — идентификатор отправителя. Он генерируется, когда приватный ключ отправителя подписывает транзакцию и подтверждает, что отправитель авторизовал эту транзакцию.
-- `nonce` - последовательно возрастающий счетчик, указывающий номер транзакции аккаунта.
-- `value` — количество ETH для передачи от отправителя получателю (номинировано в WEI, где 1ETH равен 1e+18wei).
+- `nonce` — последовательно увеличивающийся счетчик, который указывает номер транзакции для аккаунта
+- `value` — сумма ETH для перевода от отправителя получателю (указывается в WEI, где 1 ETH равен 1e+18 wei)
- `input data` — необязательное поле для включения произвольных данных.
-- `gasLimit` — максимальное количество единиц газа, которое может быть использовано транзакцией. [EVM](/developers/docs/evm/opcodes) определяет количество газа, необходимого для каждого этапа вычислений.
-- `maxPriorityFeePerGas` — максимальная цена потребленного газа, которая будет включена в качестве чаевых для валидатора.
-- `maxFeePerGas` — максимальная комиссия за единицу газа, которая будет выплачена за транзакцию (включая `baseFeePerGas` и `maxPriorityFeePerGas`).
+- `gasLimit` — максимальное количество единиц газа, которое может быть потреблено транзакцией. [EVM](/developers/docs/evm/opcodes) определяет количество единиц газа, необходимое для каждого вычислительного шага
+- `maxPriorityFeePerGas` — максимальная цена потребленного газа, которая будет включена в качестве чаевых для валидатора
+- `maxFeePerGas` — максимальная комиссия за единицу газа, которую готовы заплатить за транзакцию (включая `baseFeePerGas` и `maxPriorityFeePerGas`)
-Газ — это ссылка на вычисления, необходимые для обработки транзакции валидатором. Пользователи должны платить за это вычисление. `GasLimit` и `maxPriorityFeePerGas` определяют максимальную комиссию за транзакцию, выплачиваемую валидатору. [Подробнее о газе](/developers/docs/gas/).
+Газ — это ссылка на вычисления, необходимые для обработки транзакции валидатором. Пользователи должны платить за это вычисление. `gasLimit` и `maxPriorityFeePerGas` определяют максимальную комиссию за транзакцию, выплачиваемую валидатору. [Подробнее о газе](/developers/docs/gas/).
Объект транзакции будет выглядеть примерно так:
@@ -99,22 +100,26 @@ lang: ru
}
```
-- `raw` — это подписанная транзакция в закодированной форме [рекурсивной длины префикса (RLP)](/developers/docs/data-structures-and-encoding/rlp).
-- `tx` — это подписанная транзакция в форме JSON.
+- `raw` — это подписанная транзакция в закодированном виде с [префиксом рекурсивной длины (RLP)](/developers/docs/data-structures-and-encoding/rlp)
+- `tx` — это подписанная транзакция в формате JSON
С помощью хэша подписи можно криптографически доказать, что транзакция пришла от отправителя и была отправлена в сеть.
### Поле данных {#the-data-field}
-В подавляющем большинстве операций доступ к контракту осуществляется с внешнего аккаунта. Большинство контрактов написаны на Solidity и интерпретируют свое поле данных в соответствии с [бинарным интерфейсом приложения (ABI)](/glossary/#abi).
+В подавляющем большинстве операций доступ к контракту осуществляется с внешнего аккаунта.
+Большинство контрактов написаны на Solidity и интерпретируют свое поле данных в соответствии с [двоичным интерфейсом приложения (ABI)](/glossary/#abi).
-Первые четыре байта указывают, какую функцию следует вызвать, используя хэш имени функции и ее аргументов. Иногда можно определить функцию по селектору, используя [эту базу данных](https://www.4byte.directory/signatures/).
+Первые четыре байта указывают, какую функцию следует вызвать, используя хэш имени функции и ее аргументов.
+Иногда можно определить функцию по селектору, используя [эту базу данных](https://www.4byte.directory/signatures/).
Остальная часть calldata — это аргументы, [закодированные в соответствии со спецификациями ABI](https://docs.soliditylang.org/en/latest/abi-spec.html#formal-specification-of-the-encoding).
-Например, рассмотрим [эту транзакцию](https://etherscan.io/tx/0xd0dcbe007569fcfa1902dae0ab8b4e078efe42e231786312289b1eee5590f6a1). Чтобы увидеть calldata, используйте **Нажмите, чтобы увидеть больше**.
+Например, давайте посмотрим на [эту транзакцию](https://etherscan.io/tx/0xd0dcbe007569fcfa1902dae0ab8b4e078efe42e231786312289b1eee5590f6a1).
+Используйте **Click to see More**, чтобы просмотреть данные calldata.
-Селектор функции — `0xa9059cbb`. Существует несколько [известных функций с такой сигнатурой](https://www.4byte.directory/signatures/?bytes4_signature=0xa9059cbb). В этом случае [исходный код контракта](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48#code) был загружен в Etherscan, поэтому мы функцию: `transfer(address, uint256)`.
+Селектор функции — `0xa9059cbb`. Существует несколько [известных функций с этой подписью](https://www.4byte.directory/signatures/?bytes4_signature=0xa9059cbb).
+В данном случае [исходный код контракта](https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48#code) был загружен в Etherscan, поэтому мы знаем, что функцией является `transfer(address,uint256)`.
Остальные данные таковы:
@@ -123,7 +128,9 @@ lang: ru
000000000000000000000000000000000000000000000000000000003b0559f4
```
-Согласно спецификациям ABI целочисленные значения (такие как адреса, которые являются 20-байтовыми целыми числами) отображаются в ABI как 32-байтовые слова, заполненные нулями спереди. Итак, мы знаем адрес `to`: [`4f6742badb049791cd9a37ea913f2bac38d01279`](https://etherscan.io/address/0x4f6742badb049791cd9a37ea913f2bac38d01279). Значение `value` равно 0x3b0559f4 = 990206452.
+Согласно спецификациям ABI целочисленные значения (такие как адреса, которые являются 20-байтовыми целыми числами) отображаются в ABI как 32-байтовые слова, заполненные нулями спереди.
+Таким образом, мы знаем, что адрес `to` — [`4f6742badb049791cd9a37ea913f2bac38d01279`](https://etherscan.io/address/0x4f6742badb049791cd9a37ea913f2bac38d01279).
+Значение `value` равно 0x3b0559f4 = 990206452.
## Типы транзакций {#types-of-transactions}
@@ -135,9 +142,9 @@ lang: ru
### О газе {#on-gas}
-Как уже упоминалось, выполнение транзакций требует затрат [газа](/developers/docs/gas/). Для простых транзакций перевода требуется 21 000 единиц газа.
+Как уже упоминалось, для выполнения транзакций требуется [газ](/developers/docs/gas/). Для простых транзакций перевода требуется 21 000 единиц газа.
-Таким образом, чтобы Боб отправил Алисе 1 ETH с `baseFeePerGas` 190 gwei и `maxPriorityFeePerGas` 10 gwei, Бобу необходимо будет заплатить следующую комиссию:
+Итак, чтобы Боб отправил Алисе 1 ETH при `baseFeePerGas` в 190 gwei и `maxPriorityFeePerGas` в 10 gwei, Бобу нужно будет заплатить следующую комиссию:
```
(190 + 10) * 21 000 = 4 200 000 gwei
@@ -145,76 +152,81 @@ lang: ru
0,0042 ETH
```
-Со счета Боба будет списано **1,0042 ETH** (1 ETH для Алисы + 0,0042 ETH как комиссия за газ)
+Со счета Боба будет списано **-1,0042 ETH** (1 ETH для Алисы + 0,0042 ETH в виде комиссии за газ).
На счет Алисы будет зачислено **+1,0 ETH**
-Сжигаемая базовая комиссия составит **-0,00399 ЕТН**
+Базовая комиссия будет сожжена **-0,00399 ETH**
-Валидатор получит **+0,000210 ETH**
+Валидатор получает чаевые **+0,000210 ETH**
-
- _Источник адаптированной диаграммы: [Ethereum EVM illustrated](https://takenobu-hs.github.io/downloads/ethereum_evm_illustrated.pdf)_
+
+_Диаграмма адаптирована из [Ethereum EVM illustrated](https://takenobu-hs.github.io/downloads/ethereum_evm_illustrated.pdf)_
Любой газ, не использованный в транзакции, возвращается в аккаунт пользователя.
-### Взаимодействия смарт-контрактов {#smart-contract-interactions}
+### Взаимодействие со смарт-контрактами {#smart-contract-interactions}
Газ необходим для любой транзакции, которая связана со смарт-контрактом.
-Смарт-контракты также могут содержать функции, известные как [`view`](https://docs.soliditylang.org/en/latest/contracts.html#view-functions) или [`pure`](https://docs.soliditylang.org/en/latest/contracts.html#pure-functions), которые не изменяют состояние контракта. Поэтому для вызова этих функций из внешней учетной записи (EOA) не требуется газ. Базовый вызов RPC для этого сценария — [`eth_call`](/developers/docs/apis/json-rpc#eth_call)
+Смарт-контракты также могут содержать функции, известные как функции [`view`](https://docs.soliditylang.org/en/latest/contracts.html#view-functions) или [`pure`](https://docs.soliditylang.org/en/latest/contracts.html#pure-functions), которые не изменяют состояние контракта. Поэтому для вызова этих функций из внешней учетной записи (EOA) не требуется газ. Основной вызов RPC для этого сценария — [`eth_call`](/developers/docs/apis/json-rpc#eth_call).
-В отличие от доступа с помощью `eth_call`, эти функции `view` или `pure` также обычно вызываются внутри (т. е. из самого контракта или из другого контракта), что требует затрат газа.
+В отличие от доступа через `eth_call`, эти функции `view` или `pure` также часто вызываются внутренне (т. е. из самого контракта или из другого контракта), что требует затрат газа.
## Жизненный цикл транзакции {#transaction-lifecycle}
После отправки транзакции происходит следующее:
-1. Хэш транзакции генерируется криптографически: `0x97d99bc7729211111a21b12c933c949d4f31684f1d6954ff477d0477538ff017`
+1. Хэш транзакции генерируется криптографически:
+ `0x97d99bc7729211111a21b12c933c949d4f31684f1d6954ff477d0477538ff017`
2. Затем транзакция транслируется в сеть и добавляется в пул транзакций, состоящий из всех других ожидающих транзакций сети.
3. Валидатор должен взять вашу транзакцию и включить ее в блок, чтобы подтвердить транзакцию и признать ее «успешной».
-4. По прошествии времени блок, содержащий вашу транзакцию, будет обновлен до уровня «утвержденный», а затем «завершенный» Эти обновления дают гораздо больше уверенности, что ваша сделка была успешной, а изменить ее невозможно. Как только блок «завершен», он может быть изменен только при атаке на сетевом уровне, которая стоила бы миллиарды долларов.
+4. По прошествии времени блок, содержащий вашу транзакцию, будет обновлен до уровня «утвержденный», а затем «завершенный» Эти обновления делают гораздо
+ более вероятным, что ваша транзакция была успешной и никогда не будет изменена. Как только блок будет «финализирован», его можно будет изменить
+ только путем атаки на уровне сети, которая будет стоить многие миллиарды долларов.
-## Визуализация {#a-visual-demo}
+## Наглядная демонстрация {#a-visual-demo}
Посмотрите, как Остин рассказывает о транзакциях, газе и майнинге.
-## Типизированная оболочка транзакций {#typed-transaction-envelope}
+## Типизированный конверт транзакции {#typed-transaction-envelope}
-Ethereum изначально имел один формат транзакций. Каждая транзакция содержала значение nonce, цену на газ, лимит газа, адрес, значение, данные, v, r и s. Эти поля [кодируются с помощью RLP](/developers/docs/data-structures-and-encoding/rlp/), чтобы выглядеть примерно так:
+Ethereum изначально имел один формат транзакций. Каждая транзакция содержала значение nonce, цену на газ, лимит газа, адрес, значение, данные, v, r и s. Эти поля [кодируются с помощью RLP](/developers/docs/data-structures-and-encoding/rlp/), и выглядят примерно так:
`RLP([nonce, gasPrice, gasLimit, to, value, data, v, r, s])`
-Ethereum эволюционировал до поддержки нескольких типов транзакций, чтобы реализовать новые функции, такие как списки доступа и [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559), без влияния на старые форматы транзакций.
+Ethereum эволюционировал и теперь поддерживает несколько типов транзакций, что позволяет реализовывать новые функции, такие как списки доступа и [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559), не затрагивая устаревшие форматы транзакций.
-[EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) — это то, что позволяет такие действия. Транзакции интепретируются следующим образом:
+Именно [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) обеспечивает такое поведение. Транзакции интепретируются следующим образом:
`TransactionType || TransactionPayload`
Где поля определяются так:
-- `TransactionType`: число между 0 и 0x7f, в общей сложности 128 возможных типов транзакций.
-- `TransactionPayload` — произвольный байтовый массив, определяемый типом транзакции.
+- `TransactionType` — число от 0 до 0x7f, что в сумме дает 128 возможных типов транзакций.
+- `TransactionPayload` — произвольный массив байтов, определяемый типом транзакции.
-В зависимости от значения `TransactionType` транзакция может быть классифицирована как
+В зависимости от значения `TransactionType` транзакцию можно классифицировать как:
-1. **Транзакции типа 0 (устаревшие).** Исходный формат транзакций, используемый с момента запуска Ethereum. Они не включают функций от [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559), таких как динамические расчеты затрат газа или списки доступа для смарт-контрактов. Устаревшие транзакции не имеют конкретного префикса, указывающего их тип в сериализованной форме, и начинаются с байта `0xf8` при использовании кодировки [RLP](/developers/docs/data-structures-and-encoding/rlp). Значение TransactionType для этих транзакций — `0x0`.
+1. **Транзакции типа 0 (устаревшие):** исходный формат транзакций, используемый с момента запуска Ethereum. Они не включают такие функции из [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559), как динамический расчет комиссии за газ или списки доступа для смарт-контрактов. Устаревшие транзакции не имеют определенного префикса, указывающего их тип в сериализованной форме, и начинаются с байта `0xf8` при использовании кодировки [префикса рекурсивной длины (RLP)](/developers/docs/data-structures-and-encoding/rlp). Значение `TransactionType` для этих транзакций равно `0x0`.
-2. **Транзакции типа 1.** Эти транзакции, которые появились в [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) в результате [обновления Berlin](/ethereum-forks/#berlin), включают параметр `accessList`. В этом списке указываются адреса и ключи хранения, к которым транзакция должна получить доступ, что потенциально помогает снизить затраты на [газ](/developers/docs/gas/) в сложных транзакциях, связанных со смарт-контрактами. Изменения рынка комиссий EIP-1559 не включены в транзакции типа 1. Транзакции типа 1 также включают параметр `yParity`, который может быть либо `0x0`, либо `0x1`, что указывает на четность y-значения подписи secp256k1. Они идентифицируются по байту в начале — `0x01`, а их значение TransactionType — `0x1`.
+2. **Транзакции типа 1:** Эти транзакции, представленные в [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) в рамках [обновления Berlin](/ethereum-forks/#berlin) для Ethereum, включают параметр `accessList`. Этот список определяет адреса и ключи хранилища, к которым, как ожидается, будет обращаться транзакция, что помогает потенциально снизить затраты на [газ](/developers/docs/gas/) для сложных транзакций с участием смарт-контрактов. Изменения рынка комиссий EIP-1559 не включены в транзакции типа 1. Транзакции типа 1 также включают параметр `yParity`, который может быть либо `0x0`, либо `0x1` и указывает на четность y-значения подписи secp256k1. Они идентифицируются по начальному байту `0x01`, а их значение `TransactionType` равно `0x1`.
-3. **Транзакции типа 2**, обычно называемые транзакциями EIP-1559, являются транзакциями, которые появились в [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) в результате [обновления London](/ethereum-forks/#london). Они стали стандартным типом транзакций в сети Ethereum. Эти транзакции создают новый механизм рынка комиссий, который повышает предсказуемость, разделяя комиссию за транзакции на базовую и приоритетную. Они начинаются с байта `0x02` и включают такие поля, как `maxPriorityFeePerGas` и `maxFeePerGas`. Транзакции типа 2 теперь являются стандартными из-за их гибкости и эффективности, особенно предпочтительны в периоды высокой загруженности сети из-за их способности помочь пользователям управлять комиссиями за транзакции более предсказуемо. Значение TransactionType для этих транзакций — `0x2`.
+3. **Транзакции типа 2**, широко известные как транзакции EIP-1559, это транзакции, введенные в [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) в рамках [обновления London](/ethereum-forks/#london) для Ethereum. Они стали стандартным типом транзакций в сети Ethereum. Эти транзакции создают новый механизм рынка комиссий, который повышает предсказуемость, разделяя комиссию за транзакции на базовую и приоритетную. Они начинаются с байта `0x02` и включают такие поля, как `maxPriorityFeePerGas` и `maxFeePerGas`. Транзакции типа 2 теперь являются стандартными из-за их гибкости и эффективности, особенно предпочтительны в периоды высокой загруженности сети из-за их способности помочь пользователям управлять комиссиями за транзакции более предсказуемо. Значение `TransactionType` для этих транзакций равно `0x2`.
+4. **Транзакции типа 3 (Blob)** были введены в [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) в рамках [обновления Dencun](/ethereum-forks/#dencun) в Ethereum. Эти транзакции предназначены для более эффективной обработки данных типа blob (Binary Large Objects), что особенно выгодно для ролл-апов уровня 2, поскольку они предоставляют способ публикации данных в сети Ethereum по более низкой цене. Транзакции типа blob включают дополнительные поля, такие как `blobVersionedHashes`, `maxFeePerBlobGas` и `blobGasPrice`. Они начинаются с байта `0x03`, а их значение `TransactionType` равно `0x3`. Транзакции типа blob представляют собой значительное улучшение доступности данных и возможностей масштабирования Ethereum.
+5. **Транзакции типа 4** были введены в [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) в рамках [обновления Pectra](/roadmap/pectra/) в Ethereum. Эти транзакции разработаны с учетом прямой совместимости с абстракцией аккаунтов. Они позволяют EOA временно вести себя как аккаунты смарт-контрактов, не нарушая их исходной функциональности. Они включают параметр `authorization_list`, который указывает смарт-контракт, которому EOA делегирует свои полномочия. После транзакции поле кода EOA будет содержать адрес делегированного смарт-контракта.
-## Дополнительные ресурсы {#further-reading}
+## Дополнительные материалы {#further-reading}
-- [EIP-2718: типизированная оболочка транзакции](https://eips.ethereum.org/EIPS/eip-2718)
+- [EIP-2718: Типизированный конверт транзакции](https://eips.ethereum.org/EIPS/eip-2718)
_Знаете ресурс сообщества, который вам пригодился? Измените эту страницу и добавьте его!_
-## Похожие темы {#related-topics}
+## Смежные темы {#related-topics}
- [Аккаунты](/developers/docs/accounts/)
- [Виртуальная машина Ethereum (EVM)](/developers/docs/evm/)
diff --git a/public/content/translations/ru/developers/docs/web2-vs-web3/index.md b/public/content/translations/ru/developers/docs/web2-vs-web3/index.md
index dc7f9dd5261..be14067979e 100644
--- a/public/content/translations/ru/developers/docs/web2-vs-web3/index.md
+++ b/public/content/translations/ru/developers/docs/web2-vs-web3/index.md
@@ -1,12 +1,12 @@
---
-title: Сравнение Web2 и Web3
-description:
+title: "Сравнение Web2 и Web3"
+description: "Сравните централизованные сервисы Web2 с децентрализованными приложениями Web3, построенными на блокчейн технологии Ethereum."
lang: ru
---
Web2 — это версия Интернета, известная сегодня большинству из нас. Интернет, в котором доминируют компании, предоставляющие услуги в обмен на ваши личные данные. Web3 в контексте Ethereum относится к децентрализованным приложениям, работающим на технологии блокчейна. Это приложения, которые позволяют каждому из нас пользоваться ими, не монетизируя при этом наши личные данные.
-Ищете более удобный для начинающих ресурс? Взгляните на наше [введение в web3](/web3/).
+Ищете более удобный для начинающих ресурс? Ознакомьтесь с нашим [введением в Web3](/web3/).
## Преимущества Web3 {#web3-benefits}
@@ -17,12 +17,12 @@ Web2 — это версия Интернета, известная сегодн
- Платежи производятся через собственный токен, эфир (ETH).
- Ethereum является полным по Тьюрингу. Это означает, что вы можете программировать практически что угодно.
-## Практическое сравнение {#practical-comparisons}
+## Практические сравнения {#practical-comparisons}
-| Web2 | Web3 |
-| ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
-| Twitter может цензурировать любой аккаунт или твит | Твиты Web3 не смогут быть подвергнуты цензуре, так как контроль в такой сети децентрализован |
-| Платежный сервис может принять решение не разрешать оплату определенных видов работ | Платежные приложения Web3 не требуют личных данных и не могут препятствовать проведению платежей |
+| Web2 | Web3 |
+| ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Twitter может цензурировать любой аккаунт или твит | Твиты Web3 не смогут быть подвергнуты цензуре, так как контроль в такой сети децентрализован |
+| Платежный сервис может принять решение не разрешать оплату определенных видов работ | Платежные приложения Web3 не требуют личных данных и не могут препятствовать проведению платежей |
| Серверы для приложений, связанных с гиг-экономикой, могут выйти из строя и повлиять на доход работников | Серверы Web3 не могут выходить из строя: в качестве бэкэнда они используют Ethereum — децентрализованную сеть из тысяч компьютеров |
Это не значит, что все сервисы должны быть превращены в децентрализованные приложения. Эти примеры иллюстрируют основные различия между сервисами web2 и web3.
@@ -40,23 +40,23 @@ Web2 — это версия Интернета, известная сегодн
В таблице, представленной ниже, перечислены некоторые общие преимущества и недостатки централизованных и децентрализованных цифровых сетей.
-| Централизованные системы | Децентрализованные системы |
-| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Низкий диаметр сети (то есть когда все участники подключены к одному крупному источнику); информация распространяется быстро, так как распространение осуществляется центральным органом, располагающим большим количеством вычислительных ресурсов. | Наиболее удаленные друг от друга участники сети потенциально могут находиться на огромном расстоянии. Информации, транслируемой с одной стороны сети, может потребоваться много времени, чтобы достичь другой стороны. |
-| Обычно более высокая производительность (более высокая пропускная способность, затрата меньшего объема общих вычислительных ресурсов) и простота реализации. | Обычно более низкая производительность (меньшая пропускная способность, затрачивается больше общих вычислительных ресурсов) и повышенная сложность в реализации. |
-| В случае противоречивых данных разрешение понятно и легко: окончательный источник истины — это центральный орган. | Если одноранговые узлы приходят к противоречивым заключениям о состоянии данных, по которым участники должны синхронизироваться, то для разрешения таких споров необходим протокол (часто сложный). |
-| Единая точка отказа: злоумышленники могут вывести из строя сеть, выбрав целью центральный орган. | Нет единой точки отказа: сеть может продолжать функционировать, даже если большая часть участников подвергнется атаке или отключению от сети. |
-| Координация между участниками сети намного проще и осуществляется центральным органом управления. Центральный орган может заставить участников сети применять обновления системы или протоколов с минимальными трениями. | Координация часто затруднена, поскольку ни один участник системы не имеет последнего слова при принятии решений на уровне сети, обновлении протокола и т. д. В худшем случае сеть приблизится к расколу из-за разногласий по поводу изменений протокола. |
-| Центральный орган может подвергать данные цензуре, потенциально отрезая части сети от взаимодействия с остальной ее частью. | Подвергать что-либо цензуре становится намного сложнее, поскольку информация может распространяться по сети разными способами. |
-| Участие в сети контролируется центральным органом. | Кто угодно может участвовать в сети, так как там нет никаких «надзирателей». В идеале цена участия очень низкая. |
+| Централизованные системы | Децентрализованные системы |
+| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Низкий диаметр сети (то есть когда все участники подключены к одному крупному источнику); информация распространяется быстро, так как распространение осуществляется центральным органом, располагающим большим количеством вычислительных ресурсов. | Наиболее удаленные друг от друга участники сети потенциально могут находиться на огромном расстоянии. Информации, транслируемой с одной стороны сети, может потребоваться много времени, чтобы достичь другой стороны. |
+| Обычно более высокая производительность (более высокая пропускная способность, затрата меньшего объема общих вычислительных ресурсов) и простота реализации. | Обычно более низкая производительность (меньшая пропускная способность, затрачивается больше общих вычислительных ресурсов) и повышенная сложность в реализации. |
+| В случае противоречивых данных разрешение понятно и легко: окончательный источник истины — это центральный орган. | Если одноранговые узлы приходят к противоречивым заключениям о состоянии данных, по которым участники должны синхронизироваться, то для разрешения таких споров необходим протокол (часто сложный). |
+| Единая точка отказа: злоумышленники могут вывести из строя сеть, выбрав целью центральный орган. | Нет единой точки отказа: сеть может продолжать функционировать, даже если большая часть участников подвергнется атаке или отключению от сети. |
+| Координация между участниками сети намного проще и осуществляется центральным органом управления. Центральный орган может заставить участников сети применять обновления системы или протоколов с минимальными трениями. | Координация часто затруднена, поскольку ни один участник системы не имеет последнего слова при принятии решений на уровне сети, обновлении протокола и т. д. В худшем случае сеть приблизится к расколу из-за разногласий по поводу изменений протокола. |
+| Центральный орган может подвергать данные цензуре, потенциально отрезая части сети от взаимодействия с остальной ее частью. | Подвергать что-либо цензуре становится намного сложнее, поскольку информация может распространяться по сети разными способами. |
+| Участие в сети контролируется центральным органом. | Кто угодно может участвовать в сети, так как там нет никаких «надзирателей». В идеале цена участия очень низкая. |
Обратите внимание, что это общие шаблоны, которые могут не выполняться в каждой из сетей. Более того, в действительности степень централизации или децентрализации сети выражается промежуточным значением; ни одна сеть не является полностью централизованной или полностью децентрализованной.
-## Дополнительные ресурсы {#further-reading}
+## Дополнительные материалы {#further-reading}
- [Что такое Web3?](/web3/) — _ethereum.org_
-- [Архитектура приложения Web 3.0](https://www.preethikasireddy.com/post/the-architecture-of-a-web-3-0-application) — _Прити Касиредди_
-- [Значение децентрализации](https://medium.com/@VitalikButerin/the-meaning-of-decentralization-a0c92b76a274) — _6 февраля 2017 г., Виталик Бутерин_
-- [Почему важна децентрализация](https://medium.com/s/story/why-decentralization-matters-5e3f79f7638e) — _18 февраля 2018 г., Крис Диксон_
-- [Что такое Web 3.0 и почему это важно](https://medium.com/fabric-ventures/what-is-web-3-0-why-it-matters-934eb07f3d2b) — _31 декабря 2019 г., Макс Мерш и Ричард Мюрхед_
-- [Зачем нам Web 3.0](https://medium.com/@gavofyork/why-we-need-web-3-0-5da4f2bf95ab) — _12 сентября 2018 г., Гэвин Вуд_
+- [Архитектура Web 3.0-приложения](https://www.preethikasireddy.com/post/the-architecture-of-a-web-3-0-application) — _Preethi Kasireddy_
+- [Значение децентрализации](https://medium.com/@VitalikButerin/the-meaning-of-decentralization-a0c92b76a274) _6 февраля 2017 г. — Виталик Бутерин_
+- [Почему децентрализация имеет значение](https://onezero.medium.com/why-decentralization-matters-5e3f79f7638e) _18 февраля 2018 г. — Крис Диксон_
+- [Что такое Web 3.0 и почему это важно](https://medium.com/fabric-ventures/what-is-web-3-0-why-it-matters-934eb07f3d2b) _31 декабря 2019 г. — Макс Мерш и Ричард Мьюирхед_
+- [Зачем нам нужен Web 3.0](https://gavofyork.medium.com/why-we-need-web-3-0-5da4f2bf95ab) _12 сентября 2018 г. — Гэвин Вуд_
diff --git a/public/content/translations/ru/developers/docs/wrapped-eth/index.md b/public/content/translations/ru/developers/docs/wrapped-eth/index.md
index a1a9bec063f..c23bb3a7c2f 100644
--- a/public/content/translations/ru/developers/docs/wrapped-eth/index.md
+++ b/public/content/translations/ru/developers/docs/wrapped-eth/index.md
@@ -1,6 +1,6 @@
---
-title: Что такое обернутый эфир (WETH)
-description: Введение в обернутый эфир (WETH) — ERC20-совместимую оболочку для эфира (ETH).
+title: "Что такое обернутый эфир (WETH)"
+description: "Введение в обернутый эфир (WETH) — ERC20-совместимую оболочку для эфира (ETH)."
lang: ru
---
@@ -35,19 +35,16 @@ Ether (ETH) — основная валюта Ethereum. Он используе
Вы платите комиссию за газ для обертывания или развертывания ETH с использованием контракта WETH.
-
WETH обычно считается безопасным, поскольку он основан на простом и проверенном смарт-контракте. Контракт WETH также официально проверен, что является высшим стандартом безопасности для смарт-контрактов на Ethereum.
-
Помимо [канонической реализации WETH](https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2), описанной на этой странице, существуют и другие варианты. Это могут быть специальные токены, созданные разработчиками приложений, или версии, выпущенные в других блокчейнах, которые могут вести себя по-разному или иметь другие параметры безопасности. **Всегда дважды проверяйте информацию о токене, чтобы знать, с какой реализацией WETH вы взаимодействуете.**
-
@@ -55,7 +52,6 @@ WETH обычно считается безопасным, поскольку о
- [Основная сеть Ethereum](https://etherscan.io/token/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)
- [Arbitrum](https://arbiscan.io/token/0x82af49447d8a07e3bd95bd0d56f35241523fbab1)
- [Optimism](https://optimistic.etherscan.io/token/0x4200000000000000000000000000000000000006)
-
## Дополнительные материалы {#further-reading}
diff --git a/public/content/translations/ru/developers/tutorials/a-developers-guide-to-ethereum-part-one/index.md b/public/content/translations/ru/developers/tutorials/a-developers-guide-to-ethereum-part-one/index.md
new file mode 100644
index 00000000000..be7b0cd5baf
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/a-developers-guide-to-ethereum-part-one/index.md
@@ -0,0 +1,300 @@
+---
+title: "Введение в Ethereum для разработчиков на Python, часть 1"
+description: "Введение в разработку для Ethereum, особенно полезное для тех, кто знаком с языком программирования Python"
+author: Marc Garreau
+lang: ru
+tags: [ "python", "web3.py" ]
+skill: beginner
+published: 2020-09-08
+source: Snake charmers
+sourceUrl: https://snakecharmers.ethereum.org/a-developers-guide-to-ethereum-pt-1/
+---
+
+Итак, вы слышали об Ethereum и готовы нырнуть в кроличью нору? В этой статье мы быстро рассмотрим некоторые основы блокчейна, а затем вы сможете поработать с симулированным узлом Ethereum: читать данные блоков, проверять балансы счетов и отправлять транзакции. Попутно мы выделим различия между традиционными способами создания приложений и этой новой децентрализованной парадигмой.
+
+## Рекомендуемые предварительные требования {#soft-prerequisites}
+
+Эта статья призвана быть доступной для широкого круга разработчиков. В статье будут использоваться [инструменты Python](/developers/docs/programming-languages/python/), но они являются лишь средством для демонстрации идей. Ничего страшного, если вы не являетесь Python-разработчиком. Однако я сделаю несколько предположений о том, что вы уже знаете, чтобы мы могли быстрее перейти к особенностям Ethereum.
+
+Предположения:
+
+- Вы умеете работать в терминале,
+- Вы написали несколько строк кода на Python,
+- На вашем компьютере установлен Python версии 3.6 или выше (настоятельно рекомендуется использовать [виртуальную среду](https://realpython.com/effective-python-environment/#virtual-environments)), и
+- вы использовали `pip`, установщик пакетов Python.
+ Опять же, если что-то из этого неверно или вы не планируете воспроизводить код из этой статьи, вы, скорее всего, все равно сможете без проблем следить за изложением.
+
+## Коротко о блокчейнах {#blockchains-briefly}
+
+Существует много способов описать Ethereum, но в его основе лежит блокчейн. Блокчейны состоят из серии блоков, так что давайте с этого и начнем. Проще говоря, каждый блок в блокчейне Ethereum — это просто набор метаданных и список транзакций. В формате JSON это выглядит примерно так:
+
+```json
+{
+ "number": 1234567,
+ "hash": "0xabc123...",
+ "parentHash": "0xdef456...",
+ ...,
+ "transactions": [...]
+}
+```
+
+Каждый [блок](/developers/docs/blocks/) содержит ссылку на предшествующий ему блок; `parentHash` — это просто хэш предыдущего блока.
+
+Примечание. Ethereum регулярно использует хэш-функции для получения значений фиксированного размера («хэшей»). Хэши играют важную роль в Ethereum, но пока вы можете спокойно считать их уникальными идентификаторами.
+
+
+
+_По сути, блокчейн — это связный список; каждый блок содержит ссылку на предыдущий._
+
+Эта структура данных не нова, но новаторскими являются правила (т. е. одноранговые протоколы), которые управляют сетью. Центрального органа управления нет; участники сети должны сотрудничать для ее поддержания и конкурировать за право решать, какие транзакции включать в следующий блок. Итак, когда вы хотите отправить деньги другу, вам нужно будет объявить об этой транзакции в сети, а затем дождаться ее включения в один из следующих блоков.
+
+Единственный способ для блокчейна проверить, что деньги действительно были отправлены от одного пользователя другому, — это использовать собственную валюту (т. е. созданную и управляемую) этого блокчейна. В Ethereum эта валюта называется ether (эфир), а блокчейн Ethereum содержит единственную официальную запись о балансах счетов.
+
+## Новая парадигма {#a-new-paradigm}
+
+Этот новый децентрализованный технологический стек породил новые инструменты для разработчиков. Такие инструменты существуют для многих языков программирования, но мы будем рассматривать их через призму Python. Повторюсь: даже если Python не является языком вашего выбора, вам не составит особого труда следить за изложением.
+
+Python-разработчики, которые хотят взаимодействовать с Ethereum, скорее всего, воспользуются [Web3.py](https://web3py.readthedocs.io/). Web3.py — это библиотека, которая значительно упрощает подключение к узлу Ethereum, а также отправку и получение данных от него.
+
+Примечание. «Узел Ethereum» и «клиент Ethereum» используются как взаимозаменяемые понятия. В любом случае речь идет о программном обеспечении, которое запускает участник сети Ethereum. Это программное обеспечение может читать данные блоков, получать обновления при добавлении в цепь новых блоков, объявлять о новых транзакциях и многое другое. Технически клиент — это программное обеспечение, а узел — это компьютер, на котором это программное обеспечение запущено.
+
+[Клиенты Ethereum](/developers/docs/nodes-and-clients/) могут быть настроены для доступа по [IPC](https://wikipedia.org/wiki/Inter-process_communication), HTTP или через веб-сокеты, поэтому Web3.py должен будет отражать эту конфигурацию. Web3.py называет эти варианты подключения **провайдерами**. Вам нужно будет выбрать одного из трех провайдеров, чтобы связать экземпляр Web3.py с вашим узлом.
+
+
+
+_Настройте узел Ethereum и Web3.py для обмена данными по одному и тому же протоколу, например, IPC на этой диаграмме._
+
+После правильной настройки Web3.py вы можете начать взаимодействовать с блокчейном. Вот пара примеров использования Web3.py в качестве предварительного ознакомления с тем, что нас ждет впереди:
+
+```python
+# чтение данных блока:
+w3.eth.get_block('latest')
+
+# отправка транзакции:
+w3.eth.send_transaction({'from': ..., 'to': ..., 'value': ...})
+```
+
+## Установка {#installation}
+
+В этом пошаговом руководстве мы будем работать только в интерпретаторе Python. Мы не будем создавать никаких каталогов, файлов, классов или функций.
+
+Примечание. В приведенных ниже примерах команды, начинающиеся с `$`, предназначены для запуска в терминале. (Не вводите символ `$`, он просто обозначает начало строки.)
+
+Сначала установите [IPython](https://ipython.org/), чтобы получить удобную среду для изучения. Помимо прочих функций, IPython предлагает автодополнение по клавише Tab, что значительно упрощает изучение возможностей Web3.py.
+
+```bash
+pip install ipython
+```
+
+Web3.py опубликован под названием `web3`. Установить его можно так:
+
+```bash
+pip install web3
+```
+
+И еще кое-что: позже мы будем симулировать блокчейн, для чего потребуется еще пара зависимостей. Их можно установить с помощью следующей команды:
+
+```bash
+pip install 'web3[tester]'
+```
+
+Все готово к работе!
+
+Примечание. Пакет `web3[tester]` работает вплоть до версии Python 3.10.xx
+
+## Запускаем песочницу {#spin-up-a-sandbox}
+
+Откройте новую среду Python, запустив `ipython` в своем терминале. Это похоже на запуск `python`, но с большим количеством дополнительных возможностей.
+
+```bash
+ipython
+```
+
+Будет выведена информация о версиях Python и IPython, после чего вы увидите приглашение для ввода:
+
+```python
+In [1]:
+```
+
+Теперь вы смотрите в интерактивную оболочку Python. По сути, это песочница для экспериментов. Если вы дошли до этого места, то пора импортировать Web3.py:
+
+```python
+In [1]: from web3 import Web3
+```
+
+## Знакомство с модулем Web3 {#introducing-the-web3-module}
+
+Помимо того, что модуль [Web3](https://web3py.readthedocs.io/en/stable/overview.html#base-api) является шлюзом в Ethereum, он предлагает несколько удобных функций. Давайте рассмотрим некоторые из них.
+
+В приложении Ethereum часто возникает необходимость конвертировать номиналы валют. Модуль Web3 предоставляет для этого пару вспомогательных методов: [from_wei](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.from_wei) и [to_wei](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.to_wei).
+
+
+Примечание. Компьютеры печально известны плохой обработкой десятичной арифметики. Чтобы обойти это, разработчики часто хранят суммы в долларах в центах. Например, товар с ценой $5,99 может храниться в базе данных как 599.
+
+Аналогичный подход используется при обработке транзакций в ether. Однако вместо двух десятичных знаков у ether их 18! Самая маленькая единица ether называется wei, и именно это значение указывается при отправке транзакций.
+
+1 ether = 1000000000000000000 wei
+
+1 wei = 0.000000000000000001 ether
+
+
+
+Попробуйте конвертировать некоторые значения в wei и обратно. Обратите внимание, что [многие из номиналов](https://web3py.readthedocs.io/en/stable/troubleshooting.html#how-do-i-convert-currency-denominations) между ether и wei имеют свои названия. Один из наиболее известных среди них — **gwei**, так как в нем часто указывают комиссию за транзакции.
+
+```python
+In [2]: Web3.to_wei(1, 'ether')
+Out[2]: 1000000000000000000
+
+In [3]: Web3.from_wei(500000000, 'gwei')
+Out[3]: Decimal('0.5')
+```
+
+Другие служебные методы модуля Web3 включают преобразователи форматов данных (например, [`toHex`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.toHex)), вспомогательные функции для адресов (например, [`isAddress`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.isAddress)) и хэш-функции (например, [`keccak`](https://web3py.readthedocs.io/en/stable/web3.main.html#web3.Web3.keccak)). Многие из них будут рассмотрены далее в этой серии статей. Чтобы просмотреть все доступные методы и свойства, воспользуйтесь автодополнением IPython, набрав `Web3`. и дважды нажав клавишу Tab после точки.
+
+## Общение с блокчейном {#talk-to-the-chain}
+
+Вспомогательные методы — это прекрасно, но давайте перейдем к блокчейну. Следующим шагом является настройка Web3.py для взаимодействия с узлом Ethereum. Здесь у нас есть возможность использовать провайдеры IPC, HTTP или Websocket.
+
+Мы не будем идти по этому пути, но пример полного рабочего процесса с использованием HTTP-провайдера может выглядеть примерно так:
+
+- Скачайте узел Ethereum, например, [Geth](https://geth.ethereum.org/).
+- Запустите Geth в одном окне терминала и дождитесь синхронизации с сетью. Порт HTTP по умолчанию — `8545`, но его можно настроить.
+- Сообщите Web3.py, чтобы он подключался к узлу через HTTP, по адресу `localhost:8545`.
+ `w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545'))`
+- Используйте экземпляр `w3` для взаимодействия с узлом.
+
+Хотя это один из «реальных» способов, процесс синхронизации занимает часы и не является необходимым, если вам просто нужна среда разработки. Для этой цели Web3.py предоставляет четвертый провайдер — **EthereumTesterProvider**. Этот тестовый провайдер подключается к симулированному узлу Ethereum с более мягкими правами доступа и поддельной валютой для экспериментов.
+
+
+
+_EthereumTesterProvider подключается к симулированному узлу и удобен для быстрой организации сред разработки._
+
+Этот симулированный узел называется [eth-tester](https://github.com/ethereum/eth-tester), и мы установили его в рамках команды `pip install web3[tester]`. Настроить Web3.py для использования этого тестового провайдера очень просто:
+
+```python
+In [4]: w3 = Web3(Web3.EthereumTesterProvider())
+```
+
+Теперь вы готовы к серфингу по блокчейну! Вообще-то так не говорят. Я только что это выдумал. Давайте совершим краткий тур.
+
+## Краткий тур {#the-quick-tour}
+
+Первым делом — проверка работоспособности:
+
+```python
+In [5]: w3.is_connected()
+Out[5]: True
+```
+
+Поскольку мы используем тестовый провайдер, эта проверка не очень информативна, но если она не удалась, скорее всего, вы что-то не так набрали при создании экземпляра переменной `w3`. Перепроверьте, что вы включили внутренние скобки, т. е. `Web3.EthereumTesterProvider()`.
+
+## Остановка № 1: [аккаунты](/developers/docs/accounts/) {#tour-stop-1-accounts}
+
+Для удобства тестовый провайдер создал несколько аккаунтов и предварительно пополнил их тестовым эфиром.
+
+Сначала давайте посмотрим список этих аккаунтов:
+
+```python
+In [6]: w3.eth.accounts
+Out[6]: ['0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
+ '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF',
+ '0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69', ...]
+```
+
+Если вы выполните эту команду, вы должны увидеть список из десяти строк, начинающихся с `0x`. Каждая из них является **публичным адресом** и в некотором роде аналогична номеру расчетного счета. Вы можете предоставить этот адрес тому, кто хочет отправить вам эфир.
+
+Как уже упоминалось, тестовый провайдер предварительно пополнил каждый из этих аккаунтов некоторым количеством тестового эфира. Давайте узнаем, сколько средств на первом аккаунте:
+
+```python
+In [7]: w3.eth.get_balance(w3.eth.accounts[0])
+Out[7]: 1000000000000000000000000
+```
+
+Как много нулей! Прежде чем вы побежите в поддельный банк, вспомните урок о номиналах валюты. Суммы в ether выражаются в наименьшем номинале — wei. Конвертируйте это в ether:
+
+```python
+In [8]: w3.from_wei(1000000000000000000000000, 'ether')
+Out[8]: Decimal('1000000')
+```
+
+Один миллион тестовых ether — тоже неплохо.
+
+## Остановка № 2: данные блока {#tour-stop-2-block-data}
+
+Давайте посмотрим на состояние этого симулированного блокчейна:
+
+```python
+In [9]: w3.eth.get_block('latest')
+Out[9]: AttributeDict({
+ 'number': 0,
+ 'hash': HexBytes('0x9469878...'),
+ 'parentHash': HexBytes('0x0000000...'),
+ ...
+ 'transactions': []
+})
+```
+
+О блоке возвращается много информации, но здесь следует отметить лишь несколько моментов:
+
+- Номер блока — ноль, независимо от того, как давно вы настроили тестовый провайдер. В отличие от реальной сети Ethereum, которая добавляет новый блок каждые 12 секунд, эта симуляция будет ждать, пока вы не дадите ей какую-нибудь работу.
+- `transactions` — это пустой список по той же причине: мы еще ничего не делали. Этот первый блок — **пустой блок**, он нужен просто для того, чтобы запустить цепочку.
+- Обратите внимание, что `parentHash` — это просто набор пустых байтов. Это означает, что это первый блок в цепочке, также известный как **генезис-блок**.
+
+## Остановка № 3: [транзакции](/developers/docs/transactions/) {#tour-stop-3-transactions}
+
+Мы застряли на нулевом блоке, пока не появится ожидающая транзакция, так что давайте создадим ее. Отправьте несколько тестовых ether с одного аккаунта на другой:
+
+```python
+In [10]: tx_hash = w3.eth.send_transaction({
+ 'from': w3.eth.accounts[0],
+ 'to': w3.eth.accounts[1],
+ 'value': w3.to_wei(3, 'ether'),
+ 'gas': 21000
+})
+```
+
+Обычно в этот момент вам пришлось бы ждать несколько секунд, пока ваша транзакция не будет включена в новый блок. Полный процесс выглядит примерно так:
+
+1. Отправьте транзакцию и сохраните ее хэш. Пока блок, содержащий транзакцию, не будет создан и распространен, транзакция находится в состоянии «ожидания».
+ `tx_hash = w3.eth.send_transaction({ … })`
+2. Дождитесь включения транзакции в блок:
+ `w3.eth.wait_for_transaction_receipt(tx_hash)`
+3. Продолжите логику приложения. Чтобы просмотреть успешную транзакцию:
+ `w3.eth.get_transaction(tx_hash)`
+
+Наша симулированная среда мгновенно добавит транзакцию в новый блок, так что мы можем сразу же просмотреть транзакцию:
+
+```python
+In [11]: w3.eth.get_transaction(tx_hash)
+Out[11]: AttributeDict({
+ 'hash': HexBytes('0x15e9fb95dc39...'),
+ 'blockNumber': 1,
+ 'transactionIndex': 0,
+ 'from': '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf',
+ 'to': '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF',
+ 'value': 3000000000000000000,
+ ...
+})
+```
+
+Здесь вы увидите некоторые знакомые детали: поля `from`, `to` и `value` должны соответствовать входным данным нашего вызова `send_transaction`. Еще один обнадеживающий момент заключается в том, что эта транзакция была включена как первая транзакция (`'transactionIndex': 0`) в блок номер 1.
+
+Мы также можем легко проверить успешность этой транзакции, проверив балансы двух задействованных аккаунтов. Три ether должны были переместиться с одного на другой.
+
+```python
+In [12]: w3.eth.get_balance(w3.eth.accounts[0])
+Out[12]: 999996999979000000000000
+
+In [13]: w3.eth.get_balance(w3.eth.accounts[1])
+Out[13]: 1000003000000000000000000
+```
+
+Последний выглядит хорошо! Баланс изменился с 1 000 000 до 1 000 003 ether. Но что случилось с первым аккаунтом? Похоже, он потерял немного больше трех ether. Увы, в жизни ничто не дается бесплатно, и использование публичной сети Ethereum требует, чтобы вы компенсировали другим участникам их поддерживающую роль. С аккаунта, отправившего транзакцию, была списана небольшая комиссия за транзакцию. Эта комиссия представляет собой объем сожженного газа (21 000 единиц газа за перевод ETH), умноженный на базовую комиссию, которая варьируется в зависимости от активности сети, плюс чаевые, которые получает валидатор, включивший транзакцию в блок.
+
+Подробнее о [газе](/developers/docs/gas/#post-london)
+
+Примечание. В публичной сети комиссии за транзакции являются переменными и зависят от спроса в сети и от того, насколько быстро вы хотите, чтобы транзакция была обработана. Если вас интересует, как рассчитываются комиссии, см. мой предыдущий пост о том, как транзакции включаются в блок.
+
+## И выдохнем {#and-breathe}
+
+Мы уже довольно долго этим занимаемся, так что это хорошее место, чтобы сделать перерыв. Кроличья нора продолжается, и мы продолжим ее исследование во второй части этой серии статей. Некоторые из будущих тем: подключение к реальному узлу, смарт-контракты и токены. Остались вопросы? Дайте мне знать! Ваши отзывы повлияют на то, куда мы будем двигаться дальше. Запросы принимаются через [Twitter](https://twitter.com/wolovim).
diff --git a/public/content/translations/ru/developers/tutorials/all-you-can-cache/index.md b/public/content/translations/ru/developers/tutorials/all-you-can-cache/index.md
new file mode 100644
index 00000000000..da8342a96f3
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/all-you-can-cache/index.md
@@ -0,0 +1,867 @@
+---
+title: "Что можно кешировать"
+description: "Узнайте, как создать и использовать кеширующий контракт для более дешевых транзакций ролл-апа"
+author: Ori Pomerantz
+tags: [ "уровень 2", "кеширование", "хранилище" ]
+skill: intermediate
+published: 2022-09-15
+lang: ru
+---
+
+При использовании ролл-апов стоимость байта в транзакции намного выше, чем стоимость слота в хранилище. Поэтому имеет смысл кешировать ончейн как можно больше информации.
+
+В этой статье вы узнаете, как создавать и использовать кеширующий контракт таким образом, чтобы любое значение параметра, которое, скорее всего, будет использоваться несколько раз, кешировалось и было доступно для использования (после первого раза) с гораздо меньшим количеством байтов, и как писать оффчейн-код, использующий этот кеш.
+
+Если вы хотите пропустить статью и просто посмотреть исходный код, [он находится здесь](https://github.com/qbzzt/20220915-all-you-can-cache). Стек разработки — [Foundry](https://getfoundry.sh/introduction/installation/).
+
+## Общий дизайн {#overall-design}
+
+Для простоты предположим, что все параметры транзакции имеют тип `uint256` и длину 32 байта. Когда мы получаем транзакцию, мы разбираем каждый параметр следующим образом:
+
+1. Если первый байт равен `0xFF`, взять следующие 32 байта как значение параметра и записать его в кеш.
+
+2. Если первый байт равен `0xFE`, взять следующие 32 байта как значение параметра, но _не_ записывать его в кеш.
+
+3. Для любого другого значения взять старшие четыре бита как количество дополнительных байтов, а младшие четыре бита — как старшие значащие биты ключа кеша. Вот несколько примеров:
+
+ | Байты в calldata | Ключ кеша |
+ | :--------------- | --------: |
+ | 0x0F | 0x0F |
+ | 0x10,0x10 | 0x10 |
+ | 0x12,0xAC | 0x02AC |
+ | 0x2D,0xEA, 0xD6 | 0x0DEAD6 |
+
+## Манипулирование кешем {#cache-manipulation}
+
+Кеш реализован в [`Cache.sol`](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/src/Cache.sol). Давайте рассмотрим его построчно.
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.13;
+
+
+contract Cache {
+
+ bytes1 public constant INTO_CACHE = 0xFF;
+ bytes1 public constant DONT_CACHE = 0xFE;
+```
+
+Эти константы используются для интерпретации особых случаев, когда мы предоставляем всю информацию и решаем, записывать ее в кеш или нет. Запись в кеш требует двух операций [`SSTORE`](https://www.evm.codes/#55) в ранее неиспользованные слоты хранилища стоимостью 22100 ед. газа каждая, поэтому мы делаем ее необязательной.
+
+```solidity
+
+ mapping(uint => uint) public val2key;
+```
+
+[Сопоставление (mapping)](https://www.geeksforgeeks.org/solidity/solidity-mappings/) между значениями и их ключами. Эта информация необходима для кодирования значений перед отправкой транзакции.
+
+```solidity
+ // Ячейка n содержит значение для ключа n+1, потому что нам нужно сохранить
+ // ноль как значение «не в кеше».
+ uint[] public key2val;
+```
+
+Мы можем использовать массив для сопоставления ключей со значениями, потому что мы сами назначаем ключи, и для простоты делаем это последовательно.
+
+```solidity
+ function cacheRead(uint _key) public view returns (uint) {
+ require(_key <= key2val.length, "Чтение неинициализированной записи кеша");
+ return key2val[_key-1];
+ } // cacheRead
+```
+
+Чтение значения из кеша.
+
+```solidity
+ // Записать значение в кеш, если его там еще нет
+ // Public только для того, чтобы тест работал
+ function cacheWrite(uint _value) public returns (uint) {
+ // Если значение уже есть в кеше, вернуть текущий ключ
+ if (val2key[_value] != 0) {
+ return val2key[_value];
+ }
+```
+
+Нет смысла помещать одно и то же значение в кеш более одного раза. Если значение уже есть, просто верните существующий ключ.
+
+```solidity
+ // Поскольку 0xFE — это особый случай, самый большой ключ, который может
+ // содержать кеш, — это 0x0D, за которым следуют 15 значений 0xFF. Если длина кеша уже
+ // настолько велика, произойдет сбой.
+ // 1 2 3 4 5 6 7 8 9 A B C D E F
+ require(key2val.length+1 < 0x0DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
+ "переполнение кеша");
+```
+
+Я не думаю, что мы когда-нибудь получим кеш такого большого размера (примерно 1,8\*1037 записей, для хранения которых потребуется около 1027 ТБ). Однако я достаточно стар, чтобы помнить [«640 КБ всегда будет достаточно»](https://quoteinvestigator.com/2011/09/08/640k-enough/). Эта проверка очень «дешевая» (не требует больших затрат).
+
+```solidity
+ // Записать значение, используя следующий ключ
+ val2key[_value] = key2val.length+1;
+```
+
+Добавление обратного поиска (от значения к ключу).
+
+```solidity
+ key2val.push(_value);
+```
+
+Добавление прямого поиска (от ключа к значению). Поскольку мы присваиваем значения последовательно, мы можем просто добавить его после последнего значения массива.
+
+```solidity
+ return key2val.length;
+ } // cacheWrite
+```
+
+Возвращается новая длина `key2val`, которая является ячейкой, где хранится новое значение.
+
+```solidity
+ function _calldataVal(uint startByte, uint length)
+ private pure returns (uint)
+```
+
+Эта функция считывает из calldata значение произвольной длины (до 32 байт, размер слова).
+
+```solidity
+ {
+ uint _retVal;
+
+ require(length < 0x21,
+ "_calldataVal length limit is 32 bytes");
+ require(length + startByte <= msg.data.length,
+ "_calldataVal trying to read beyond calldatasize");
+```
+
+Эта функция является внутренней, поэтому, если остальная часть кода написана правильно, эти тесты не требуются. Однако они не требуют больших затрат, так что их можно оставить.
+
+```solidity
+ assembly {
+ _retVal := calldataload(startByte)
+ }
+```
+
+Этот код написан на [Yul](https://docs.soliditylang.org/en/v0.8.16/yul.html). Он считывает 32-байтовое значение из calldata. Это работает, даже если calldata заканчивается до `startByte+32`, потому что неинициализированное пространство в EVM считается нулевым.
+
+```solidity
+ _retVal = _retVal >> (256-length*8);
+```
+
+Нам не обязательно нужно 32-байтовое значение. Это избавляет от лишних байтов.
+
+```solidity
+ return _retVal;
+ } // _calldataVal
+
+
+ // Считать один параметр из calldata, начиная с _fromByte
+ function _readParam(uint _fromByte) internal
+ returns (uint _nextByte, uint _parameterValue)
+ {
+```
+
+Считывание одного параметра из calldata. Обратите внимание, что нам нужно вернуть не только прочитанное значение, но и местоположение следующего байта, потому что параметры могут иметь длину от 1 до 33 байт.
+
+```solidity
+ // Первый байт говорит нам, как интерпретировать остальные
+ uint8 _firstByte;
+
+ _firstByte = uint8(_calldataVal(_fromByte, 1));
+```
+
+Solidity пытается уменьшить количество ошибок, запрещая потенциально опасные [неявные преобразования типов](https://docs.soliditylang.org/en/v0.8.16/types.html#implicit-conversions). Понижение, например с 256 бит до 8 бит, должно быть явным.
+
+```solidity
+
+ // Прочитать значение, но не записывать его в кеш
+ if (_firstByte == uint8(DONT_CACHE))
+ return(_fromByte+33, _calldataVal(_fromByte+1, 32));
+
+ // Прочитать значение и записать его в кеш
+ if (_firstByte == uint8(INTO_CACHE)) {
+ uint _param = _calldataVal(_fromByte+1, 32);
+ cacheWrite(_param);
+ return(_fromByte+33, _param);
+ }
+
+ // Если мы дошли до этого места, это означает, что нам нужно читать из кеша
+
+ // Количество дополнительных байтов для чтения
+ uint8 _extraBytes = _firstByte / 16;
+```
+
+Возьмите младший [ниббл](https://en.wikipedia.org/wiki/Nibble) и объедините его с другими байтами, чтобы прочитать значение из кеша.
+
+```solidity
+ uint _key = (uint256(_firstByte & 0x0F) << (8*_extraBytes)) +
+ _calldataVal(_fromByte+1, _extraBytes);
+
+ return (_fromByte+_extraBytes+1, cacheRead(_key));
+
+ } // _readParam
+
+
+ // Прочитать n параметров (функции знают, сколько параметров они ожидают)
+ function _readParams(uint _paramNum) internal returns (uint[] memory) {
+```
+
+Мы могли бы получить количество имеющихся у нас параметров из самой calldata, но функции, которые нас вызывают, знают, сколько параметров они ожидают. Проще позволить им сообщить нам.
+
+```solidity
+ // Параметры, которые мы читаем
+ uint[] memory params = new uint[](_paramNum);
+
+ // Параметры начинаются с 4-го байта, до этого идет сигнатура функции
+ uint _atByte = 4;
+
+ for(uint i=0; i<_paramNum; i++) {
+ (_atByte, params[i]) = _readParam(_atByte);
+ }
+```
+
+Считывайте параметры, пока не получите необходимое их количество. Если мы выйдем за конец calldata, `_readParams` отменит вызов.
+
+```solidity
+
+ return(params);
+ } // readParams
+
+ // Для тестирования _readParams, протестируйте чтение четырех параметров
+ function fourParam() public
+ returns (uint256,uint256,uint256,uint256)
+ {
+ uint[] memory params;
+ params = _readParams(4);
+ return (params[0], params[1], params[2], params[3]);
+ } // fourParam
+```
+
+Одним из больших преимуществ Foundry является то, что он позволяет писать тесты на Solidity ([см. Тестирование кеша ниже](#testing-the-cache)). Это значительно упрощает модульные тесты. Это функция, которая считывает четыре параметра и возвращает их, чтобы тест мог проверить их правильность.
+
+```solidity
+ // Получить значение, вернуть байты, которые будут его кодировать (используя кеш, если это возможно)
+ function encodeVal(uint _val) public view returns(bytes memory) {
+```
+
+`encodeVal` — это функция, которую вызывает оффчейн-код, чтобы помочь создать calldata, использующую кеш. Она получает одно значение и возвращает байты, которые его кодируют. Эта функция является `view`, поэтому она не требует транзакции и при внешнем вызове не требует затрат газа.
+
+```solidity
+ uint _key = val2key[_val];
+
+ // Значения еще нет в кеше, добавляем его
+ if (_key == 0)
+ return bytes.concat(INTO_CACHE, bytes32(_val));
+```
+
+В [EVM](/developers/docs/evm/) все неинициализированное хранилище считается нулевым. Поэтому, если мы ищем ключ для значения, которого там нет, мы получаем ноль. В этом случае байты, которые его кодируют, — это `INTO_CACHE` (чтобы в следующий раз оно было кешировано), за которым следует фактическое значение.
+
+```solidity
+ // Если ключ <0x10, вернуть его как один байт
+ if (_key < 0x10)
+ return bytes.concat(bytes1(uint8(_key)));
+```
+
+С одиночными байтами проще всего. Мы просто используем [`bytes.concat`](https://docs.soliditylang.org/en/v0.8.16/types.html#the-functions-bytes-concat-and-string-concat), чтобы превратить тип `bytes` в массив байтов, который может быть любой длины. Несмотря на название, она отлично работает и при наличии только одного аргумента.
+
+```solidity
+ // Двухбайтовое значение, закодированное как 0x1vvv
+ if (_key < 0x1000)
+ return bytes.concat(bytes2(uint16(_key) | 0x1000));
+```
+
+Когда у нас есть ключ меньше 163, мы можем выразить его в двух байтах. Сначала мы преобразуем `_key`, которое является 256-битным значением, в 16-битное значение и используем логическое «ИЛИ», чтобы добавить количество дополнительных байтов к первому байту. Затем мы просто преобразуем его в значение `bytes2`, которое может быть конвертировано в `bytes`.
+
+```solidity
+ // Вероятно, есть более изящный способ сделать следующие строки в виде цикла,
+ // но это функция view, поэтому я оптимизирую ее с точки зрения времени программиста и
+ // простоты.
+
+ if (_key < 16*256**2)
+ return bytes.concat(bytes3(uint24(_key) | (0x2 * 16 * 256**2)));
+ if (_key < 16*256**3)
+ return bytes.concat(bytes4(uint32(_key) | (0x3 * 16 * 256**3)));
+ .
+ .
+ .
+ if (_key < 16*256**14)
+ return bytes.concat(bytes15(uint120(_key) | (0xE * 16 * 256**14)));
+ if (_key < 16*256**15)
+ return bytes.concat(bytes16(uint128(_key) | (0xF * 16 * 256**15)));
+```
+
+Другие значения (3 байта, 4 байта и т. д.) обрабатываются таким же образом, только с полями разного размера.
+
+```solidity
+ // Если мы дошли до этого места, что-то не так.
+ revert("Ошибка в encodeVal, этого не должно было случиться");
+```
+
+Если мы дошли до этого места, это означает, что мы получили ключ, который не меньше 16\*25615. Но `cacheWrite` ограничивает ключи, поэтому мы не можем даже дойти до 14\*25616 (первый байт которого будет 0xFE, так что это будет выглядеть как `DONT_CACHE`). Но добавить тест на случай, если будущий программист допустит ошибку, не составит большого труда.
+
+```solidity
+ } // encodeVal
+
+} // Cache
+```
+
+### Тестирование кеша {#testing-the-cache}
+
+Одно из преимуществ Foundry заключается в том, что [оно позволяет писать тесты на Solidity](https://getfoundry.sh/forge/tests/overview/), что упрощает написание модульных тестов. Тесты для класса `Cache` находятся [здесь](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/test/Cache.t.sol). Поскольку код для тестов повторяется, что характерно для тестов, в этой статье объясняются только интересные части.
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.13;
+
+import "forge-std/Test.sol";
+
+
+// Необходимо запустить `forge test -vv` для console.
+import "forge-std/console.sol";
+```
+
+Это просто шаблонный код, необходимый для использования тестового пакета и `console.log`.
+
+```solidity
+import "src/Cache.sol";
+```
+
+Нам нужно знать контракт, который мы тестируем.
+
+```solidity
+contract CacheTest is Test {
+ Cache cache;
+
+ function setUp() public {
+ cache = new Cache();
+ }
+```
+
+Функция `setUp` вызывается перед каждым тестом. В этом случае мы просто создаем новый кеш, чтобы наши тесты не влияли друг на друга.
+
+```solidity
+ function testCaching() public {
+```
+
+Тесты — это функции, имена которых начинаются с `test`. Эта функция проверяет базовую функциональность кеша, записывая значения и считывая их снова.
+
+```solidity
+ for(uint i=1; i<5000; i++) {
+ cache.cacheWrite(i*i);
+ }
+
+ for(uint i=1; i<5000; i++) {
+ assertEq(cache.cacheRead(i), i*i);
+```
+
+Вот как вы проводите фактическое тестирование, используя [функции `assert...`](https://getfoundry.sh/reference/forge-std/std-assertions/). В этом случае мы проверяем, что значение, которое мы записали, является тем, которое мы прочитали. Мы можем отбросить результат `cache.cacheWrite`, потому что знаем, что ключи кеша назначаются линейно.
+
+```solidity
+ }
+ } // testCaching
+
+
+ // Кешировать одно и то же значение несколько раз, убедиться, что ключ остается
+ // прежним
+ function testRepeatCaching() public {
+ for(uint i=1; i<100; i++) {
+ uint _key1 = cache.cacheWrite(i);
+ uint _key2 = cache.cacheWrite(i);
+ assertEq(_key1, _key2);
+ }
+```
+
+Сначала мы записываем каждое значение в кеш дважды и убеждаемся, что ключи одинаковы (то есть вторая запись на самом деле не произошла).
+
+```solidity
+ for(uint i=1; i<100; i+=3) {
+ uint _key = cache.cacheWrite(i);
+ assertEq(_key, i);
+ }
+ } // testRepeatCaching
+```
+
+Теоретически может существовать ошибка, которая не влияет на последовательные записи в кеш. Так что здесь мы делаем несколько записей, которые не являются последовательными, и видим, что значения все еще не перезаписываются.
+
+```solidity
+ // Считывание uint из буфера памяти (чтобы убедиться, что мы получаем обратно параметры,
+ // которые отправили)
+ function toUint256(bytes memory _bytes, uint256 _start) internal pure
+ returns (uint256)
+```
+
+Считывание 256-битного слова из буфера `bytes memory`. Эта служебная функция позволяет нам убедиться, что мы получаем правильные результаты при выполнении вызова функции, использующей кеш.
+
+```solidity
+ {
+ require(_bytes.length >= _start + 32, "toUint256_outOfBounds");
+ uint256 tempUint;
+
+ assembly {
+ tempUint := mload(add(add(_bytes, 0x20), _start))
+ }
+```
+
+Yul не поддерживает структуры данных сложнее `uint256`, поэтому, когда вы обращаетесь к более сложной структуре данных, такой как буфер памяти `_bytes`, вы получаете адрес этой структуры. Solidity хранит значения `bytes memory` в виде 32-байтового слова, которое содержит длину, за которой следуют фактические байты, так что для того чтобы получить байт номер `_start`, нам нужно вычислить `_bytes+32+_start`.
+
+```solidity
+
+ return tempUint;
+ } // toUint256
+
+ // Сигнатура функции для fourParams() взята с
+ // https://www.4byte.directory/signatures/?bytes4_signature=0x3edc1e6d
+ bytes4 constant FOUR_PARAMS = 0x3edc1e6d;
+
+ // Просто несколько постоянных значений, чтобы убедиться, что мы получаем правильные значения
+ uint256 constant VAL_A = 0xDEAD60A7;
+ uint256 constant VAL_B = 0xBEEF;
+ uint256 constant VAL_C = 0x600D;
+ uint256 constant VAL_D = 0x600D60A7;
+```
+
+Некоторые константы, которые нам нужны для тестирования.
+
+```solidity
+ function testReadParam() public {
+```
+
+Вызов `fourParams()`, функции, которая использует `readParams`, чтобы проверить, можем ли мы правильно читать параметры.
+
+```solidity
+ address _cacheAddr = address(cache);
+ bool _success;
+ bytes memory _callInput;
+ bytes memory _callOutput;
+```
+
+Мы не можем использовать обычный механизм ABI для вызова функции, использующей кеш, поэтому нам нужно использовать низкоуровневый механизм [`.call()`](https://docs.soliditylang.org/en/v0.8.16/types.html#members-of-addresses). Этот механизм принимает на вход `bytes memory` и возвращает его (а также логическое значение) в качестве выходных данных.
+
+```solidity
+ // Первый вызов, кеш пуст
+ _callInput = bytes.concat(
+ FOUR_PARAMS,
+```
+
+Полезно, чтобы один и тот же контракт поддерживал как кешированные функции (для вызовов непосредственно из транзакций), так и некешированные функции (для вызовов из других смарт-контрактов). Для этого нам нужно продолжать полагаться на механизм Solidity для вызова правильной функции, вместо того чтобы помещать все в [функцию `fallback`](https://docs.soliditylang.org/en/v0.8.16/contracts.html#fallback-function). Это значительно упрощает компонуемость. Одного байта в большинстве случаев было бы достаточно для идентификации функции, поэтому мы тратим впустую три байта (16\*3=48 ед. газа). Однако на момент написания этой статьи эти 48 ед. газа стоят 0,07 цента, что является разумной ценой за более простой и менее подверженный ошибкам код.
+
+```solidity
+ // Первое значение, добавить его в кеш
+ cache.INTO_CACHE(),
+ bytes32(VAL_A),
+```
+
+Первое значение: флаг, указывающий, что это полное значение, которое необходимо записать в кеш, за которым следуют 32 байта значения. Остальные три значения похожи, за исключением того, что `VAL_B` не записывается в кеш, а `VAL_C` является и третьим, и четвертым параметром.
+
+```solidity
+ .
+ .
+ .
+ );
+ (_success, _callOutput) = _cacheAddr.call(_callInput);
+```
+
+Здесь мы фактически вызываем контракт `Cache`.
+
+```solidity
+ assertEq(_success, true);
+```
+
+Мы ожидаем, что звонок будет успешным.
+
+```solidity
+ assertEq(cache.cacheRead(1), VAL_A);
+ assertEq(cache.cacheRead(2), VAL_C);
+```
+
+Мы начинаем с пустого кеша, а затем добавляем `VAL_A`, а затем `VAL_C`. Мы ожидаем, что у первого будет ключ 1, а у второго — 2.
+
+```
+ assertEq(toUint256(_callOutput,0), VAL_A);
+ assertEq(toUint256(_callOutput,32), VAL_B);
+ assertEq(toUint256(_callOutput,64), VAL_C);
+ assertEq(toUint256(_callOutput,96), VAL_C);
+```
+
+На выходе мы получаем четыре параметра. Здесь мы проверяем его правильность.
+
+```solidity
+ // Второй вызов, мы можем использовать кеш
+ _callInput = bytes.concat(
+ FOUR_PARAMS,
+
+ // Первое значение в кеше
+ bytes1(0x01),
+```
+
+Ключи кеша размером менее 16 — это всего один байт.
+
+```solidity
+ // Второе значение, не добавляйте его в кеш
+ cache.DONT_CACHE(),
+ bytes32(VAL_B),
+
+ // Третье и четвертое значения, одинаковые значения
+ bytes1(0x02),
+ bytes1(0x02)
+ );
+ .
+ .
+ .
+ } // testReadParam
+```
+
+Тесты после вызова идентичны тестам после первого вызова.
+
+```solidity
+ function testEncodeVal() public {
+```
+
+Эта функция похожа на `testReadParam`, за исключением того, что вместо явной записи параметров мы используем `encodeVal()`.
+
+```solidity
+ .
+ .
+ .
+ _callInput = bytes.concat(
+ FOUR_PARAMS,
+ cache.encodeVal(VAL_A),
+ cache.encodeVal(VAL_B),
+ cache.encodeVal(VAL_C),
+ cache.encodeVal(VAL_D)
+ );
+ .
+ .
+ .
+ assertEq(_callInput.length, 4+1*4);
+ } // testEncodeVal
+```
+
+Единственный дополнительный тест в `testEncodeVal()` — это проверка правильности длины `_callInput`. Для первого вызова это 4+33\*4. Для второго, где каждое значение уже находится в кеше, это 4+1\*4.
+
+```solidity
+ // Тестирование encodeVal, когда ключ состоит более чем из одного байта
+ // Максимум три байта, потому что заполнение кеша до четырех байт занимает
+ // слишком много времени.
+ function testEncodeValBig() public {
+ // Поместить несколько значений в кеш.
+ // Для простоты используйте ключ n для значения n.
+ for(uint i=1; i<0x1FFF; i++) {
+ cache.cacheWrite(i);
+ }
+```
+
+Приведенная выше функция `testEncodeVal` записывает в кеш только четыре значения, поэтому [часть функции, которая работает с многобайтовыми значениями](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/src/Cache.sol#L144-L171), не проверяется. Но этот код сложен и подвержен ошибкам.
+
+Первая часть этой функции представляет собой цикл, который записывает все значения от 1 до 0x1FFF в кеш по порядку, так что мы сможем закодировать эти значения и узнать, куда они пойдут.
+
+```solidity
+ .
+ .
+ .
+
+ _callInput = bytes.concat(
+ FOUR_PARAMS,
+ cache.encodeVal(0x000F), // Один байт 0x0F
+ cache.encodeVal(0x0010), // Два байта 0x1010
+ cache.encodeVal(0x0100), // Два байта 0x1100
+ cache.encodeVal(0x1000) // Три байта 0x201000
+ );
+```
+
+Проверьте значения в один, два и три байта. Мы не будем тестировать дальше, потому что для записи достаточного количества записей стека потребуется слишком много времени (не менее 0x10000000, примерно четверть миллиарда).
+
+```solidity
+ .
+ .
+ .
+ .
+ } // testEncodeValBig
+
+
+ // Тестируем, что при слишком маленьком буфере мы получаем отмену
+ function testShortCalldata() public {
+```
+
+Проверьте, что происходит в нештатном случае, когда параметров недостаточно.
+
+```solidity
+ .
+ .
+ .
+ (_success, _callOutput) = _cacheAddr.call(_callInput);
+ assertEq(_success, false);
+ } // testShortCalldata
+```
+
+Так как он отменяется, мы должны получить результат `false`.
+
+```
+ // Вызов с ключами кеша, которых нет
+ function testNoCacheKey() public {
+ .
+ .
+ .
+ _callInput = bytes.concat(
+ FOUR_PARAMS,
+
+ // Первое значение, добавить его в кеш
+ cache.INTO_CACHE(),
+ bytes32(VAL_A),
+
+ // Второе значение
+ bytes1(0x0F),
+ bytes2(0x1234),
+ bytes11(0xA10102030405060708090A)
+ );
+```
+
+Эта функция получает четыре совершенно законных параметра, за исключением того, что кеш пуст, поэтому в нем нет значений для чтения.
+
+```solidity
+ .
+ .
+ .
+ // Тестируем, что при слишком большом буфере все работает
+ function testLongCalldata() public {
+ address _cacheAddr = address(cache);
+ bool _success;
+ bytes memory _callInput;
+ bytes memory _callOutput;
+
+ // Первый вызов, кеш пуст
+ _callInput = bytes.concat(
+ FOUR_PARAMS,
+
+ // Первое значение, добавить его в кеш
+ cache.INTO_CACHE(), bytes32(VAL_A),
+
+ // Второе значение, добавить его в кеш
+ cache.INTO_CACHE(), bytes32(VAL_B),
+
+ // Третье значение, добавить его в кеш
+ cache.INTO_CACHE(), bytes32(VAL_C),
+
+ // Четвертое значение, добавить его в кеш
+ cache.INTO_CACHE(), bytes32(VAL_D),
+
+ // И еще одно значение для «удачи»
+ bytes4(0x31112233)
+ );
+```
+
+Эта функция отправляет пять значений. Мы знаем, что пятое значение игнорируется, потому что оно не является допустимой записью кеша, что привело бы к отмене, если бы оно не было включено.
+
+```solidity
+ (_success, _callOutput) = _cacheAddr.call(_callInput);
+ assertEq(_success, true);
+ .
+ .
+ .
+ } // testLongCalldata
+
+} // CacheTest
+
+```
+
+## Пример приложения {#a-sample-app}
+
+Писать тесты на Solidity — это, конечно, хорошо, но в конечном счете, чтобы быть полезным, децентрализованное приложение должно уметь обрабатывать запросы извне сети. В этой статье показано, как использовать кеширование в децентрализованном приложении с помощью `WORM`, что означает «Write Once, Read Many» (запись один раз, чтение много раз). Если ключ еще не записан, вы можете записать в него значение. Если ключ уже записан, вы получите отмену.
+
+### Контракт {#the-contract}
+
+[Вот этот контракт](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/src/WORM.sol). Он в основном повторяет то, что мы уже сделали с `Cache` и `CacheTest`, так что мы рассмотрим только интересные части.
+
+```solidity
+import "./Cache.sol";
+
+contract WORM is Cache {
+```
+
+Самый простой способ использовать `Cache` — это унаследовать его в нашем собственном контракте.
+
+```solidity
+ function writeEntryCached() external {
+ uint[] memory params = _readParams(2);
+ writeEntry(params[0], params[1]);
+ } // writeEntryCached
+```
+
+Эта функция похожа на `fourParam` в `CacheTest` выше. Поскольку мы не следуем спецификациям ABI, лучше не объявлять никаких параметров в функции.
+
+```solidity
+ // Упростить вызов
+ // Сигнатура функции для writeEntryCached() взята с
+ // https://www.4byte.directory/signatures/?bytes4_signature=0xe4e4f2d3
+ bytes4 constant public WRITE_ENTRY_CACHED = 0xe4e4f2d3;
+```
+
+Внешний код, который вызывает `writeEntryCached`, должен будет вручную создавать calldata, а не использовать `worm.writeEntryCached`, потому что мы не следуем спецификациям ABI. Наличие этого постоянного значения просто облегчает его написание.
+
+Обратите внимание, что хотя мы определяем `WRITE_ENTRY_CACHED` как переменную состояния, для ее считывания извне необходимо использовать функцию-геттер для нее, `worm.WRITE_ENTRY_CACHED()`.
+
+```solidity
+ function readEntry(uint key) public view
+ returns (uint _value, address _writtenBy, uint _writtenAtBlock)
+```
+
+Функция чтения — это `view`, поэтому она не требует транзакции и не расходует газ. В результате использование кеша для параметра не дает никаких преимуществ. С функциями view лучше использовать стандартный, более простой механизм.
+
+### Тестовый код {#the-testing-code}
+
+[Это тестовый код для контракта](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/test/WORM.t.sol). Опять же, давайте рассмотрим только то, что интересно.
+
+```solidity
+ function testWReadWrite() public {
+ worm.writeEntry(0xDEAD, 0x60A7);
+
+ vm.expectRevert(bytes("entry already written"));
+ worm.writeEntry(0xDEAD, 0xBEEF);
+```
+
+[Так (`vm.expectRevert`)](https://book.getfoundry.sh/cheatcodes/expect-revert#expectrevert) мы указываем в тесте Foundry, что следующий вызов должен завершиться неудачей, и сообщаемую причину сбоя. Это применимо, когда мы используем синтаксис `.()`, а не создаем calldata и вызываем контракт с помощью низкоуровневого интерфейса (`.call()` и т. д.).
+
+```solidity
+ function testReadWriteCached() public {
+ uint cacheGoat = worm.cacheWrite(0x60A7);
+```
+
+Здесь мы используем тот факт, что `cacheWrite` возвращает ключ кеша. Это не то, что мы ожидаем использовать в продакшене, потому что `cacheWrite` изменяет состояние и, следовательно, может вызываться только во время транзакции. Транзакции не возвращают значения, если у них есть результаты, эти результаты должны генерироваться как события. Таким образом, возвращаемое значение `cacheWrite` доступно только из ончейн-кода, а ончейн-код не нуждается в кешировании параметров.
+
+```solidity
+ (_success,) = address(worm).call(_callInput);
+```
+
+Вот как мы сообщаем Solidity, что, хотя `.call()` имеет два возвращаемых значения, нас интересует только первое.
+
+```solidity
+ (_success,) = address(worm).call(_callInput);
+ assertEq(_success, false);
+```
+
+Поскольку мы используем низкоуровневую функцию `.call()`, мы не можем использовать `vm.expectRevert()` и должны смотреть на логическое значение успеха, которое мы получаем от вызова.
+
+```solidity
+ event EntryWritten(uint indexed key, uint indexed value);
+
+ .
+ .
+ .
+
+ _callInput = bytes.concat(
+ worm.WRITE_ENTRY_CACHED(), worm.encodeVal(a), worm.encodeVal(b));
+ vm.expectEmit(true, true, false, false);
+ emit EntryWritten(a, b);
+ (_success,) = address(worm).call(_callInput);
+```
+
+Это способ, которым мы проверяем, что код [правильно генерирует событие](https://getfoundry.sh/reference/cheatcodes/expect-emit/) в Foundry.
+
+### Клиент {#the-client}
+
+Единственное, что вы не получаете с тестами Solidity, — это код JavaScript, который можно вырезать и вставить в свое собственное приложение. Чтобы написать этот код, я развернул WORM в [Optimism Goerli](https://community.optimism.io/docs/useful-tools/networks/#optimism-goerli), новой тестовой сети [Optimism](https://www.optimism.io/). Он находится по адресу [`0xd34335b1d818cee54e3323d3246bd31d94e6a78a`](https://goerli-optimism.etherscan.io/address/0xd34335b1d818cee54e3323d3246bd31d94e6a78a).
+
+[Вы можете увидеть код JavaScript для клиента здесь](https://github.com/qbzzt/20220915-all-you-can-cache/blob/main/javascript/index.js). Чтобы использовать его:
+
+1. Клонируйте git-репозиторий:
+
+ ```sh
+ git clone https://github.com/qbzzt/20220915-all-you-can-cache.git
+ ```
+
+2. Установите необходимые пакеты:
+
+ ```sh
+ cd javascript
+ yarn
+ ```
+
+3. Скопируйте файл конфигурации:
+
+ ```sh
+ cp .env.example .env
+ ```
+
+4. Отредактируйте `.env` для своей конфигурации:
+
+ | Параметр | Значение |
+ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+ | MNEMONIC | Мнемоническая фраза для аккаунта, у которого достаточно ETH для оплаты транзакции. [Вы можете получить бесплатный ETH для сети Optimism Goerli здесь](https://optimismfaucet.xyz/). |
+ | OPTIMISM_GOERLI_URL | URL для Optimism Goerli. Общедоступная конечная точка `https://goerli.optimism.io` имеет ограничение по скорости, но этого достаточно для того, что нам здесь нужно. |
+
+5. Запустите `index.js`.
+
+ ```sh
+ node index.js
+ ```
+
+ Это примерное приложение сначала записывает запись в WORM, отображая calldata и ссылку на транзакцию в Etherscan. Затем оно считывает эту запись и отображает используемый ключ и значения в записи (значение, номер блока и автор).
+
+Большая часть клиента — это обычный JavaScript для децентрализованных приложений. Так что, опять же, мы рассмотрим только интересные части.
+
+```javascript
+.
+.
+.
+const main = async () => {
+ const func = await worm.WRITE_ENTRY_CACHED()
+
+ // Каждый раз нужен новый ключ
+ const key = await worm.encodeVal(Number(new Date()))
+```
+
+В данный слот можно записать только один раз, поэтому мы используем временную метку, чтобы убедиться, что мы не будем повторно использовать слоты.
+
+```javascript
+const val = await worm.encodeVal("0x600D")
+
+// Записать запись
+const calldata = func + key.slice(2) + val.slice(2)
+```
+
+Ethers ожидает, что данные вызова будут шестнадцатеричной строкой, `0x`, за которым следует четное количество шестнадцатеричных цифр. Поскольку `key` и `val` начинаются с `0x`, нам нужно удалить эти заголовки.
+
+```javascript
+const tx = await worm.populateTransaction.writeEntryCached()
+tx.data = calldata
+
+sentTx = await wallet.sendTransaction(tx)
+```
+
+Как и в случае с тестовым кодом Solidity, мы не можем вызвать кешированную функцию обычным способом. Вместо этого нам нужно использовать механизм более низкого уровня.
+
+```javascript
+ .
+ .
+ .
+ // Прочитать только что сделанную запись
+ const realKey = '0x' + key.slice(4) // удалить флаг FF
+ const entryRead = await worm.readEntry(realKey)
+ .
+ .
+ .
+```
+
+Для чтения записей можно использовать обычный механизм. Для функций `view` нет необходимости использовать кеширование параметров.
+
+## Заключение {#conclusion}
+
+Код в этой статье является доказательством концепции, цель которой — сделать идею легкой для понимания. Для готовой к производству системы вы можете захотеть реализовать некоторые дополнительные функции:
+
+- Обработка значений, которые не являются `uint256`. Например, строки.
+- Вместо глобального кеша, возможно, стоит использовать сопоставление между пользователями и кешами. Разные пользователи используют разные значения.
+- Значения, используемые для адресов, отличаются от тех, что используются для других целей. Возможно, имеет смысл иметь отдельный кеш только для адресов.
+- В настоящее время ключи кеша работают по алгоритму «первым пришел — наименьший ключ». Первые шестнадцать значений могут быть отправлены в виде одного байта. Следующие 4080 значений могут быть отправлены в виде двух байтов. Следующий примерно миллион значений — это три байта и т. д. Производственная система должна вести счетчики использования записей кеша и реорганизовывать их так, чтобы шестнадцать _наиболее распространенных_ значений занимали один байт, следующие 4080 наиболее распространенных значений — два байта и т. д.
+
+ Однако это потенциально опасная операция. Представьте себе следующую последовательность событий:
+
+ 1. Ноам Наив вызывает `encodeVal` для кодирования адреса, на который он хочет отправить токены. Этот адрес является одним из первых, используемых в приложении, поэтому закодированное значение — 0x06. Это функция `view`, а не транзакция, поэтому это происходит между Ноамом и узлом, который он использует, и никто другой об этом не знает.
+
+ 2. Оуэн Оунер запускает операцию по реорганизации кеша. Очень немногие люди действительно используют этот адрес, поэтому теперь он закодирован как 0x201122. Другому значению, 1018, присваивается 0x06.
+
+ 3. Ноам Наив отправляет свои токены на 0x06. Они отправляются на адрес `0x0000000000000000000000000de0b6b3a7640000`, и, поскольку никто не знает приватный ключ для этого адреса, они просто застревают там. Ноам _недоволен_.
+
+ Есть способы решить эту проблему и связанную с ней проблему транзакций, которые находятся в мемпуле во время реорганизации кеша, но вы должны знать об этом.
+
+Я продемонстрировал кеширование здесь с помощью Optimism, потому что я сотрудник Optimism, и это ролл-ап, который я знаю лучше всего. Но это должно работать с любым ролл-апом, который взимает минимальную плату за внутреннюю обработку, так что по сравнению с этим запись данных транзакции в L1 является основной статьей расходов.
+
+[Больше моих работ смотрите здесь](https://cryptodocguy.pro/).
+
diff --git a/public/content/translations/ru/developers/tutorials/app-plasma/index.md b/public/content/translations/ru/developers/tutorials/app-plasma/index.md
new file mode 100644
index 00000000000..eb024b56691
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/app-plasma/index.md
@@ -0,0 +1,1261 @@
+---
+title: "Напишите специфичное для приложения plasma, которое сохраняет конфиденциальность"
+description: "В этом руководстве мы создадим полусекретный банк для депозитов. Банк — это централизованный компонент; он знает баланс каждого пользователя. Однако эта информация не хранится ончейн. Вместо этого банк публикует хэш состояния. Каждый раз, когда происходит транзакция, банк публикует новый хэш вместе с доказательством с нулевым разглашением того, что у него есть подписанная транзакция, которая изменяет состояние хэша на новое. Прочитав это руководство, вы поймете не только, как использовать доказательства с нулевым разглашением, но и зачем их использовать и как делать это безопасно."
+author: Ori Pomerantz
+tags:
+ [
+ "с нулевым разглашением",
+ "сервер",
+ "офчейн",
+ "конфиденциальность"
+ ]
+skill: advanced
+lang: ru
+published: 2025-10-15
+---
+
+## Введение {#introduction}
+
+В отличие от [роллапов](/developers/docs/scaling/zk-rollups/), [плазмы](/developers/docs/scaling/plasma) используют основную сеть Ethereum для целостности, но не для доступности. В этой статье мы напишем приложение, которое ведет себя как плазма, где Ethereum гарантирует целостность (отсутствие несанкционированных изменений), но не доступность (централизованный компонент может выйти из строя и отключить всю систему).
+
+Приложение, которое мы здесь пишем, — это банк, сохраняющий конфиденциальность. Разные адреса имеют аккаунты с балансами, и они могут отправлять деньги (ETH) на другие аккаунты. Банк публикует хэши состояния (аккаунты и их балансы) и транзакции, но хранит фактические балансы оффчейн, где они могут оставаться приватными.
+
+## Проектирование {#design}
+
+Это не готовая к эксплуатации система, а учебный инструмент. Поэтому она написана с несколькими упрощающими допущениями.
+
+- Фиксированный пул аккаунтов. Существует определенное количество аккаунтов, и каждый аккаунт принадлежит предопределенному адресу. Это значительно упрощает систему, поскольку в доказательствах с нулевым разглашением сложно обрабатывать структуры данных переменного размера. Для системы, готовой к производству, мы можем использовать [корень Меркла](/developers/tutorials/merkle-proofs-for-offline-data-integrity/) в качестве хэша состояния и предоставлять доказательства Меркла для требуемых балансов.
+
+- Хранение в памяти. В производственной системе нам необходимо записывать все балансы аккаунтов на диск, чтобы сохранить их в случае перезапуска. Здесь же допустимо, если информация просто будет утеряна.
+
+- Только переводы. Производственная система потребовала бы способа вносить активы в банк и выводить их. Но здесь цель — просто проиллюстрировать концепцию, поэтому этот банк ограничен переводами.
+
+### Доказательства с нулевым разглашением {#zero-knowledge-proofs}
+
+На фундаментальном уровне доказательство с нулевым разглашением показывает, что доказывающий знает некоторые данные, _Dataprivate_, такие, что существует связь _Relationship_ между некоторыми публичными данными, _Datapublic_, и _Dataprivate_. Верификатор знает _Relationship_ и _Datapublic_.
+
+Чтобы сохранить конфиденциальность, нам необходимо, чтобы состояния и транзакции были приватными. Но для обеспечения целостности нам необходимо, чтобы [криптографический хэш](https://en.wikipedia.org/wiki/Cryptographic_hash_function) состояний был публичным. Чтобы доказать людям, которые отправляют транзакции, что эти транзакции действительно произошли, нам также нужно публиковать хэши транзакций.
+
+В большинстве случаев _Dataprivate_ — это входные данные для программы доказательства с нулевым разглашением, а _Datapublic_ — выходные.
+
+Эти поля в _Dataprivate_:
+
+- _Staten_, старое состояние
+- _Staten+1_, новое состояние
+- _Транзакция_, транзакция, которая изменяет старое состояние на новое. Эта транзакция должна включать следующие поля:
+ - _Адрес назначения_, который получает перевод
+ - _Сумма_ перевода
+ - _Nonce_ для гарантии того, что каждая транзакция может быть обработана только один раз.
+ Адрес источника не обязательно должен быть в транзакции, поскольку его можно восстановить из подписи.
+- _Подпись_, подпись, которая авторизована для выполнения транзакции. В нашем случае единственный адрес, авторизованный для выполнения транзакции, — это адрес источника. Поскольку наша система с нулевым разглашением работает именно так, нам также нужен публичный ключ аккаунта в дополнение к подписи Ethereum.
+
+Это поля в _Datapublic_:
+
+- _Hash(Staten)_ — хэш старого состояния
+- _Hash(Staten+1)_ — хэш нового состояния
+- _Hash(Transaction)_ — хэш транзакции, которая изменяет состояние со _Staten_ на _Staten+1_.
+
+Связь проверяет несколько условий:
+
+- Публичные хэши действительно являются правильными хэшами для приватных полей.
+- Транзакция при применении к старому состоянию приводит к новому состоянию.
+- Подпись исходит от адреса источника транзакции.
+
+Благодаря свойствам криптографических хэш-функций, доказательство этих условий достаточно для обеспечения целостности.
+
+### Структуры данных {#data-structures}
+
+Основная структура данных — это состояние, хранимое сервером. Для каждого аккаунта сервер отслеживает баланс аккаунта и [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce), используемый для предотвращения [атак повторного воспроизведения](https://en.wikipedia.org/wiki/Replay_attack).
+
+### Компоненты {#components}
+
+Эта система требует двух компонентов:
+
+- _Сервер_, который получает транзакции, обрабатывает их и публикует хэши в блокчейн вместе с доказательствами с нулевым разглашением.
+- _Смарт-контракт_, который хранит хэши и проверяет доказательства с нулевым разглашением, чтобы убедиться в легитимности переходов состояний.
+
+### Потоки данных и управления {#flows}
+
+Это способы, которыми различные компоненты взаимодействуют для перевода с одного аккаунта на другой.
+
+1. Веб-браузер отправляет подписанную транзакцию, запрашивая перевод со счета подписывающего на другой счет.
+
+2. Сервер проверяет, что транзакция действительна:
+
+ - У подписывающего есть счет в банке с достаточным балансом.
+ - У получателя есть счет в банке.
+
+3. Сервер вычисляет новое состояние, вычитая переведенную сумму из баланса подписывающего и добавляя ее к балансу получателя.
+
+4. Сервер вычисляет доказательство с нулевым разглашением того, что изменение состояния является действительным.
+
+5. Сервер отправляет в Ethereum транзакцию, которая включает:
+
+ - Хэш нового состояния
+ - Хэш транзакции (чтобы отправитель транзакции мог знать, что она обработана)
+ - Доказательство с нулевым разглашением, которое доказывает, что переход к новому состоянию действителен
+
+6. Смарт-контракт проверяет доказательство с нулевым разглашением.
+
+7. Если доказательство с нулевым разглашением проходит проверку, смарт-контракт выполняет следующие действия:
+ - Обновляет текущий хэш состояния на новый хэш состояния
+ - Генерирует запись в журнале с новым хэшем состояния и хэшем транзакции
+
+### Инструменты {#tools}
+
+Для клиентского кода мы будем использовать [Vite](https://vite.dev/), [React](https://react.dev/), [Viem](https://viem.sh/) и [Wagmi](https://wagmi.sh/). Это стандартные отраслевые инструменты; если вы с ними не знакомы, можете воспользоваться [этим руководством](/developers/tutorials/creating-a-wagmi-ui-for-your-contract/).
+
+Большая часть сервера написана на JavaScript с использованием [Node](https://nodejs.org/en). Часть с нулевым разглашением написана на [Noir](https://noir-lang.org/). Нам нужна версия `1.0.0-beta.10`, поэтому после того, как вы [установите Noir согласно инструкции](https://noir-lang.org/docs/getting_started/quick_start), выполните:
+
+```
+noirup -v 1.0.0-beta.10
+```
+
+Блокчейн, который мы используем, — это `anvil`, локальный тестовый блокчейн, который является частью [Foundry](https://getfoundry.sh/introduction/installation).
+
+## Реализация {#implementation}
+
+Поскольку это сложная система, мы будем реализовывать ее поэтапно.
+
+### Этап 1 — Ручное нулевое разглашение {#stage-1}
+
+На первом этапе мы подпишем транзакцию в браузере, а затем вручную предоставим информацию для доказательства с нулевым разглашением. Код с нулевым разглашением ожидает получить эту информацию в `server/noir/Prover.toml` (документация [здесь](https://noir-lang.org/docs/getting_started/project_breakdown#provertoml-1)).
+
+Чтобы увидеть это в действии:
+
+1. Убедитесь, что у вас установлены [Node](https://nodejs.org/en/download) и [Noir](https://noir-lang.org/install). Желательно установить их в UNIX-системе, такой как macOS, Linux или [WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
+
+2. Загрузите код этапа 1 и запустите веб-сервер для обслуживания клиентского кода.
+
+ ```sh
+ git clone https://github.com/qbzzt/250911-zk-bank.git -b 01-manual-zk
+ cd 250911-zk-bank
+ cd client
+ npm install
+ npm run dev
+ ```
+
+ Причина, по которой здесь нужен веб-сервер, заключается в том, что для предотвращения некоторых видов мошенничества многие кошельки (например, MetaMask) не принимают файлы, обслуживаемые непосредственно с диска.
+
+3. Откройте браузер с кошельком.
+
+4. В кошельке введите новую кодовую фразу. Обратите внимание, что это удалит вашу существующую кодовую фразу, поэтому _убедитесь, что у вас есть резервная копия_.
+
+ Кодовая фраза — `test test test test test test test test test test test junk`, стандартная тестовая кодовая фраза для anvil.
+
+5. Перейдите к [клиентскому коду](http://localhost:5173/).
+
+6. Подключитесь к кошельку и выберите аккаунт назначения и сумму.
+
+7. Нажмите **Sign** (Подписать) и подпишите транзакцию.
+
+8. Под заголовком **Prover.toml** вы найдете текст. Замените `server/noir/Prover.toml` этим текстом.
+
+9. Выполните доказательство с нулевым разглашением.
+
+ ```sh
+ cd ../server/noir
+ nargo execute
+ ```
+
+ Вывод должен быть похож на
+
+ ```
+ ori@CryptoDocGuy:~/noir/250911-zk-bank/server/noir$ nargo execute
+
+ [zkBank] Circuit witness successfully solved
+ [zkBank] Witness saved to target/zkBank.gz
+ [zkBank] Circuit output: (0x199aa62af8c1d562a6ec96e66347bf3240ab2afb5d022c895e6bf6a5e617167b, 0x0cfc0a67cb7308e4e9b254026b54204e34f6c8b041be207e64c5db77d95dd82d, 0x450cf9da6e180d6159290554ae3d8787, 0x6d8bc5a15b9037e52fb59b6b98722a85)
+ ```
+
+10. Сравните последние два значения с хэшем, который вы видите в веб-браузере, чтобы проверить, правильно ли хэшировано сообщение.
+
+#### `server/noir/Prover.toml` {#server-noir-prover-toml}
+
+[Этот файл](https://github.com/qbzzt/250911-zk-bank/blob/01-manual-zk/server/noir/Prover.toml) показывает формат информации, ожидаемый Noir.
+
+```toml
+message="send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 500 finney (milliEth) 0 "
+```
+
+Сообщение представлено в текстовом формате, что облегчает его понимание пользователем (что необходимо при подписи) и разбор кодом Noir. Сумма указана в finney, чтобы, с одной стороны, обеспечить возможность дробных переводов, а с другой — быть легко читаемой. Последнее число — это [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce).
+
+Строка имеет длину 100 символов. Доказательства с нулевым разглашением плохо работают с данными переменного размера, поэтому часто необходимо дополнять данные.
+
+```toml
+pubKeyX=["0x83",...,"0x75"]
+pubKeyY=["0x35",...,"0xa5"]
+signature=["0xb1",...,"0x0d"]
+```
+
+Эти три параметра являются массивами байтов фиксированного размера.
+
+```toml
+[[accounts]]
+address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
+balance=100_000
+nonce=0
+
+[[accounts]]
+address="0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
+balance=100_000
+nonce=0
+```
+
+Это способ задания массива структур. Для каждой записи мы указываем адрес, баланс (в milliETH, т. е. [finney](https://cryptovalleyjournal.com/glossary/finney/)) и следующее значение nonce.
+
+#### `client/src/Transfer.tsx` {#client-src-transfer-tsx}
+
+[Этот файл](https://github.com/qbzzt/250911-zk-bank/blob/01-manual-zk/client/src/Transfer.tsx) реализует обработку на стороне клиента и генерирует файл `server/noir/Prover.toml` (тот, который включает параметры с нулевым разглашением).
+
+Вот объяснение наиболее интересных частей.
+
+```tsx
+export default attrs => {
+```
+
+Эта функция создает компонент `Transfer` React, который могут импортировать другие файлы.
+
+```tsx
+ const accounts = [
+ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
+ "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
+ "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
+ "0x90F79bf6EB2c4f870365E785982E1f101E93b906",
+ "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65",
+ ]
+```
+
+Это адреса аккаунтов, адреса, созданные `test ...` кодовой фразой `test junk`. Если вы хотите использовать свои собственные адреса, просто измените это определение.
+
+```tsx
+ const account = useAccount()
+ const wallet = createWalletClient({
+ transport: custom(window.ethereum!)
+ })
+```
+
+Эти [хуки Wagmi](https://wagmi.sh/react/api/hooks) позволяют нам получить доступ к библиотеке [viem](https://viem.sh/) и кошельку.
+
+```tsx
+ const message = `send ${toAccount} ${ethAmount*1000} finney (milliEth) ${nonce}`.padEnd(100, " ")
+```
+
+Это сообщение, дополненное пробелами. Каждый раз, когда изменяется одна из переменных [`useState`](https://react.dev/reference/react/useState), компонент перерисовывается, а `message` обновляется.
+
+```tsx
+ const sign = async () => {
+```
+
+Эта функция вызывается, когда пользователь нажимает кнопку **Sign** (Подписать). Сообщение обновляется автоматически, но подпись требует подтверждения пользователя в кошельке, и мы не хотим запрашивать его без необходимости.
+
+```tsx
+ const signature = await wallet.signMessage({
+ account: fromAccount,
+ message,
+ })
+```
+
+Попросить кошелек [подписать сообщение](https://viem.sh/docs/accounts/local/signMessage).
+
+```tsx
+ const hash = hashMessage(message)
+```
+
+Получить хэш сообщения. Полезно предоставить его пользователю для отладки (кода Noir).
+
+```tsx
+ const pubKey = await recoverPublicKey({
+ hash,
+ signature
+ })
+```
+
+[Получить публичный ключ](https://viem.sh/docs/utilities/recoverPublicKey). Это необходимо для функции [Noir `ecrecover`](https://github.com/colinnielsen/ecrecover-noir).
+
+```tsx
+ setSignature(signature)
+ setHash(hash)
+ setPubKey(pubKey)
+```
+
+Установить переменные состояния. Это действие перерисовывает компонент (после завершения функции `sign`) и показывает пользователю обновленные значения.
+
+```tsx
+ let proverToml = `
+```
+
+Текст для `Prover.toml`.
+
+```tsx
+message="${message}"
+
+pubKeyX=${hexToArray(pubKey.slice(4,4+2*32))}
+pubKeyY=${hexToArray(pubKey.slice(4+2*32))}
+```
+
+Viem предоставляет нам публичный ключ в виде 65-байтовой шестнадцатеричной строки. Первый байт — `0x04`, маркер версии. За ним следуют 32 байта для `x` публичного ключа, а затем 32 байта для `y` публичного ключа.
+
+Однако Noir ожидает получить эту информацию в виде двух массивов байтов, один для `x` и один для `y`. Проще разобрать это здесь, на клиенте, а не в рамках доказательства с нулевым разглашением.
+
+Обратите внимание, что это хорошая практика в области доказательств с нулевым разглашением в целом. Код внутри доказательства с нулевым разглашением является дорогостоящим, поэтому любая обработка, которая может быть выполнена вне доказательства с нулевым разглашением, _должна_ быть выполнена вне доказательства с нулевым разглашением.
+
+```tsx
+signature=${hexToArray(signature.slice(2,-2))}
+```
+
+Подпись также предоставляется в виде 65-байтовой шестнадцатеричной строки. Однако последний байт необходим только для восстановления публичного ключа. Поскольку публичный ключ уже будет предоставлен коду Noir, он нам не нужен для проверки подписи, и код Noir его не требует.
+
+```tsx
+${accounts.map(accountInProverToml).reduce((a,b) => a+b, "")}
+`
+```
+
+Предоставьте аккаунты.
+
+```tsx
+ setProverToml(proverToml)
+ }
+
+ return (
+ <>
+
Перевод
+```
+
+Это формат HTML (точнее, [JSX](https://react.dev/learn/writing-markup-with-jsx)) компонента.
+
+#### `server/noir/src/main.nr` {#server-noir-src-main-nr}
+
+[Этот файл](https://github.com/qbzzt/250911-zk-bank/blob/01-manual-zk/server/noir/src/main.nr) является фактическим кодом с нулевым разглашением.
+
+```
+use std::hash::pedersen_hash;
+```
+
+[Хэш Педерсена](https://rya-sge.github.io/access-denied/2024/05/07/pedersen-hash-function/) предоставляется стандартной библиотекой [Noir](https://noir-lang.org/docs/noir/standard_library/cryptographic_primitives/hashes#pedersen_hash). Доказательства с нулевым разглашением обычно используют эту хэш-функцию. Ее гораздо проще вычислять внутри [арифметических схем](https://rareskills.io/post/arithmetic-circuit) по сравнению со стандартными хэш-функциями.
+
+```
+use keccak256::keccak256;
+use dep::ecrecover;
+```
+
+Эти две функции являются внешними библиотеками, определенными в [`Nargo.toml`](https://github.com/qbzzt/250911-zk-bank/blob/01-manual-zk/server/noir/Nargo.toml). Они представляют собой именно то, для чего они названы: функция, которая вычисляет [хэш keccak256](https://emn178.github.io/online-tools/keccak_256.html), и функция, которая проверяет подписи Ethereum и восстанавливает адрес Ethereum подписывающего.
+
+```
+global ACCOUNT_NUMBER : u32 = 5;
+```
+
+Noir вдохновлен [Rust](https://www.rust-lang.org/). Переменные по умолчанию являются константами. Так мы определяем глобальные константы конфигурации. В частности, `ACCOUNT_NUMBER` — это количество аккаунтов, которые мы храним.
+
+Типы данных с именем `u` — это беззнаковые числа с указанным количеством битов. Единственными поддерживаемыми типами являются `u8`, `u16`, `u32`, `u64` и `u128`.
+
+```
+global FLAT_ACCOUNT_FIELDS : u32 = 2;
+```
+
+Эта переменная используется для хэша Педерсена аккаунтов, как объяснено ниже.
+
+```
+global MESSAGE_LENGTH : u32 = 100;
+```
+
+Как объяснялось выше, длина сообщения фиксирована. Она указана здесь.
+
+```
+global ASCII_MESSAGE_LENGTH : [u8; 3] = [0x31, 0x30, 0x30];
+global HASH_BUFFER_SIZE : u32 = 26+3+MESSAGE_LENGTH;
+```
+
+[Подписи EIP-191](https://eips.ethereum.org/EIPS/eip-191) требуют буфер с 26-байтовым префиксом, за которым следует длина сообщения в ASCII и, наконец, само сообщение.
+
+```
+struct Account {
+ balance: u128,
+ address: Field,
+ nonce: u32,
+}
+```
+
+Информация, которую мы храним об аккаунте. [`Field`](https://noir-lang.org/docs/noir/concepts/data_types/fields) — это число, обычно до 253 бит, которое можно использовать непосредственно в [арифметической схеме](https://rareskills.io/post/arithmetic-circuit), реализующей доказательство с нулевым разглашением. Здесь мы используем `Field` для хранения 160-битного адреса Ethereum.
+
+```
+struct TransferTxn {
+ from: Field,
+ to: Field,
+ amount: u128,
+ nonce: u32
+}
+```
+
+Информация, которую мы храним для транзакции перевода.
+
+```
+fn flatten_account(account: Account) -> [Field; FLAT_ACCOUNT_FIELDS] {
+```
+
+Определение функции. Параметром является информация об `аккаунте`. Результатом является массив переменных `Field`, длина которого равна `FLAT_ACCOUNT_FIELDS`.
+
+```
+ let flat = [
+ account.address,
+ ((account.balance << 32) + account.nonce.into()).into(),
+ ];
+```
+
+Первое значение в массиве — это адрес аккаунта. Второе включает в себя как баланс, так и nonce. Вызовы `.into()` изменяют число на тот тип данных, которым оно должно быть. `account.nonce` — это значение `u32`, но чтобы добавить его к `account.balance << 32`, значению `u128`, оно должно быть `u128`. Это первый `.into()`. Второй преобразует результат `u128` в `Field`, чтобы он поместился в массив.
+
+```
+ flat
+}
+```
+
+В Noir функции могут возвращать значение только в конце (нет раннего возврата). Чтобы указать возвращаемое значение, вы вычисляете его непосредственно перед закрывающей скобкой функции.
+
+```
+fn flatten_accounts(accounts: [Account; ACCOUNT_NUMBER]) -> [Field; FLAT_ACCOUNT_FIELDS*ACCOUNT_NUMBER] {
+```
+
+Эта функция преобразует массив аккаунтов в массив `Field`, который можно использовать в качестве входных данных для хэша Петерсена.
+
+```
+ let mut flat: [Field; FLAT_ACCOUNT_FIELDS*ACCOUNT_NUMBER] = [0; FLAT_ACCOUNT_FIELDS*ACCOUNT_NUMBER];
+```
+
+Так вы указываете изменяемую переменную, то есть _не_ константу. Переменные в Noir всегда должны иметь значение, поэтому мы инициализируем эту переменную всеми нулями.
+
+```
+ for i in 0..ACCOUNT_NUMBER {
+```
+
+Это цикл `for`. Обратите внимание, что границы являются константами. В Noir границы циклов должны быть известны во время компиляции. Причина в том, что арифметические схемы не поддерживают управление потоком. При обработке цикла `for` компилятор просто помещает код внутри него несколько раз, по одному на каждую итерацию.
+
+```
+ let fields = flatten_account(accounts[i]);
+ for j in 0..FLAT_ACCOUNT_FIELDS {
+ flat[i*FLAT_ACCOUNT_FIELDS + j] = fields[j];
+ }
+ }
+
+ flat
+}
+
+fn hash_accounts(accounts: [Account; ACCOUNT_NUMBER]) -> Field {
+ pedersen_hash(flatten_accounts(accounts))
+}
+```
+
+Наконец, мы дошли до функции, которая хэширует массив аккаунтов.
+
+```
+fn find_account(accounts: [Account; ACCOUNT_NUMBER], address: Field) -> u32 {
+ let mut account : u32 = ACCOUNT_NUMBER;
+
+ for i in 0..ACCOUNT_NUMBER {
+ if accounts[i].address == address {
+ account = i;
+ }
+ }
+```
+
+Эта функция находит аккаунт с определенным адресом. Эта функция была бы ужасно неэффективной в стандартном коде, потому что она перебирает все аккаунты, даже после того, как нашла адрес.
+
+Однако в доказательствах с нулевым разглашением нет управления потоком. Если нам когда-либо понадобится проверить условие, мы должны проверять его каждый раз.
+
+Аналогичная вещь происходит с операторами `if`. Оператор `if` в приведенном выше цикле преобразуется в эти математические утверждения.
+
+_conditionresult = accounts[i].address == address_ // единица, если они равны, ноль в противном случае
+
+_accountnew = conditionresult\*i + (1-conditionresult)\*accountold_
+
+```rust
+ assert (account < ACCOUNT_NUMBER, f"{address} does not have an account");
+
+ account
+}
+```
+
+Функция [`assert`](https://noir-lang.org/docs/dev/noir/concepts/assert) вызывает сбой доказательства с нулевым разглашением, если утверждение ложно. В данном случае, если мы не можем найти аккаунт с соответствующим адресом. Чтобы сообщить адрес, мы используем [форматированную строку](https://noir-lang.org/docs/noir/concepts/data_types/strings#format-strings).
+
+```rust
+fn apply_transfer_txn(accounts: [Account; ACCOUNT_NUMBER], txn: TransferTxn) -> [Account; ACCOUNT_NUMBER] {
+```
+
+Эта функция применяет транзакцию перевода и возвращает новый массив аккаунтов.
+
+```rust
+ let from = find_account(accounts, txn.from);
+ let to = find_account(accounts, txn.to);
+
+ let (txnFrom, txnAmount, txnNonce, accountNonce) =
+ (txn.from, txn.amount, txn.nonce, accounts[from].nonce);
+```
+
+Мы не можем получить доступ к элементам структуры внутри форматированной строки в Noir, поэтому мы создаем пригодную для использования копию.
+
+```rust
+ assert (accounts[from].balance >= txn.amount,
+ f"{txnFrom} does not have {txnAmount} finney");
+
+ assert (accounts[from].nonce == txn.nonce,
+ f"Transaction has nonce {txnNonce}, but the account is expected to use {accountNonce}");
+```
+
+Это два условия, которые могут сделать транзакцию недействительной.
+
+```rust
+ let mut newAccounts = accounts;
+
+ newAccounts[from].balance -= txn.amount;
+ newAccounts[from].nonce += 1;
+ newAccounts[to].balance += txn.amount;
+
+ newAccounts
+}
+```
+
+Создайте новый массив аккаунтов, а затем верните его.
+
+```rust
+fn readAddress(messageBytes: [u8; MESSAGE_LENGTH]) -> Field
+```
+
+Эта функция считывает адрес из сообщения.
+
+```rust
+{
+ let mut result : Field = 0;
+
+ for i in 7..47 {
+```
+
+Адрес всегда имеет длину 20 байт (т. е. 40 шестнадцатеричных цифр) и начинается с символа №7.
+
+```rust
+ result *= 0x10;
+ if messageBytes[i] >= 48 & messageBytes[i] <= 57 { // 0-9
+ result += (messageBytes[i]-48).into();
+ }
+ if messageBytes[i] >= 65 & messageBytes[i] <= 70 { // A-F
+ result += (messageBytes[i]-65+10).into()
+ }
+ if messageBytes[i] >= 97 & messageBytes[i] <= 102 { // a-f
+ result += (messageBytes[i]-97+10).into()
+ }
+ }
+
+ result
+}
+
+fn readAmountAndNonce(messageBytes: [u8; MESSAGE_LENGTH]) -> (u128, u32)
+```
+
+Считайте сумму и nonce из сообщения.
+
+```rust
+{
+ let mut amount : u128 = 0;
+ let mut nonce: u32 = 0;
+ let mut stillReadingAmount: bool = true;
+ let mut lookingForNonce: bool = false;
+ let mut stillReadingNonce: bool = false;
+```
+
+В сообщении первое число после адреса — это сумма в finney (т. е. тысячная доля ETH) для перевода. Второе число — это nonce. Любой текст между ними игнорируется.
+
+```rust
+ for i in 48..MESSAGE_LENGTH {
+ if messageBytes[i] >= 48 & messageBytes[i] <= 57 { // 0-9
+ let digit = (messageBytes[i]-48);
+
+ if stillReadingAmount {
+ amount = amount*10 + digit.into();
+ }
+
+ if lookingForNonce { // We just found it
+ stillReadingNonce = true;
+ lookingForNonce = false;
+ }
+
+ if stillReadingNonce {
+ nonce = nonce*10 + digit.into();
+ }
+ } else {
+ if stillReadingAmount {
+ stillReadingAmount = false;
+ lookingForNonce = true;
+ }
+ if stillReadingNonce {
+ stillReadingNonce = false;
+ }
+ }
+ }
+
+ (amount, nonce)
+}
+```
+
+Возврат [кортежа](https://noir-lang.org/docs/noir/concepts/data_types/tuples) — это способ в Noir вернуть несколько значений из функции.
+
+```rust
+fn readTransferTxn(message: str) -> TransferTxn
+{
+ let mut txn: TransferTxn = TransferTxn { from: 0, to: 0, amount:0, nonce:0 };
+ let messageBytes = message.as_bytes();
+
+ txn.to = readAddress(messageBytes);
+ let (amount, nonce) = readAmountAndNonce(messageBytes);
+ txn.amount = amount;
+ txn.nonce = nonce;
+
+ txn
+}
+```
+
+Эта функция преобразует сообщение в байты, а затем преобразует суммы в `TransferTxn`.
+
+```rust
+// The equivalent to Viem's hashMessage
+// https://viem.sh/docs/utilities/hashMessage#hashmessage
+fn hashMessage(message: str) -> [u8;32] {
+```
+
+Мы смогли использовать хэш Педерсена для аккаунтов, потому что они хэшируются только внутри доказательства с нулевым разглашением. Однако в этом коде нам нужно проверить подпись сообщения, которая генерируется браузером. Для этого нам нужно следовать формату подписи Ethereum в [EIP-191](https://eips.ethereum.org/EIPS/eip-191). Это означает, что нам нужно создать объединенный буфер со стандартным префиксом, длиной сообщения в ASCII и самим сообщением, и использовать стандартный для Ethereum keccak256 для его хэширования.
+
+```rust
+ // ASCII prefix
+ let prefix_bytes = [
+ 0x19, // \x19
+ 0x45, // 'E'
+ 0x74, // 't'
+ 0x68, // 'h'
+ 0x65, // 'e'
+ 0x72, // 'r'
+ 0x65, // 'e'
+ 0x75, // 'u'
+ 0x6D, // 'm'
+ 0x20, // ' '
+ 0x53, // 'S'
+ 0x69, // 'i'
+ 0x67, // 'g'
+ 0x6E, // 'n'
+ 0x65, // 'e'
+ 0x64, // 'd'
+ 0x20, // ' '
+ 0x4D, // 'M'
+ 0x65, // 'e'
+ 0x73, // 's'
+ 0x73, // 's'
+ 0x61, // 'a'
+ 0x67, // 'g'
+ 0x65, // 'e'
+ 0x3A, // ':'
+ 0x0A // '\n'
+ ];
+```
+
+Чтобы избежать случаев, когда приложение просит пользователя подписать сообщение, которое может быть использовано как транзакция или для какой-либо другой цели, EIP-191 указывает, что все подписанные сообщения начинаются с символа 0x19 (недействительный символ ASCII), за которым следует `Ethereum Signed Message:` и новая строка.
+
+```rust
+ let mut buffer: [u8; HASH_BUFFER_SIZE] = [0u8; HASH_BUFFER_SIZE];
+ for i in 0..26 {
+ buffer[i] = prefix_bytes[i];
+ }
+
+ let messageBytes : [u8; MESSAGE_LENGTH] = message.as_bytes();
+
+ if MESSAGE_LENGTH <= 9 {
+ for i in 0..1 {
+ buffer[i+26] = ASCII_MESSAGE_LENGTH[i];
+ }
+
+ for i in 0..MESSAGE_LENGTH {
+ buffer[i+26+1] = messageBytes[i];
+ }
+ }
+
+ if MESSAGE_LENGTH >= 10 & MESSAGE_LENGTH <= 99 {
+ for i in 0..2 {
+ buffer[i+26] = ASCII_MESSAGE_LENGTH[i];
+ }
+
+ for i in 0..MESSAGE_LENGTH {
+ buffer[i+26+2] = messageBytes[i];
+ }
+ }
+
+ if MESSAGE_LENGTH >= 100 {
+ for i in 0..3 {
+ buffer[i+26] = ASCII_MESSAGE_LENGTH[i];
+ }
+
+ for i in 0..MESSAGE_LENGTH {
+ buffer[i+26+3] = messageBytes[i];
+ }
+ }
+
+ assert(MESSAGE_LENGTH < 1000, "Messages whose length is over three digits are not supported");
+```
+
+Обрабатывать длины сообщений до 999 и выдавать ошибку, если она больше. Я добавил этот код, хотя длина сообщения является константой, потому что это упрощает ее изменение. В производственной системе вы, вероятно, просто предположите, что `MESSAGE_LENGTH` не изменится ради лучшей производительности.
+
+```rust
+ keccak256::keccak256(buffer, HASH_BUFFER_SIZE)
+}
+```
+
+Используйте стандартную функцию Ethereum `keccak256`.
+
+```rust
+fn signatureToAddressAndHash(
+ message: str,
+ pubKeyX: [u8; 32],
+ pubKeyY: [u8; 32],
+ signature: [u8; 64]
+ ) -> (Field, Field, Field) // address, first 16 bytes of hash, last 16 bytes of hash
+{
+```
+
+Эта функция проверяет подпись, что требует хэша сообщения. Затем она предоставляет нам адрес, который ее подписал, и хэш сообщения. Хэш сообщения предоставляется в виде двух значений `Field`, потому что их проще использовать в остальной части программы, чем массив байтов.
+
+Нам нужно использовать два значения `Field`, потому что вычисления с полями выполняются [по модулю](https://en.wikipedia.org/wiki/Modulo) большого числа, но это число обычно меньше 256 бит (иначе было бы трудно выполнять эти вычисления в EVM).
+
+```rust
+ let hash = hashMessage(message);
+
+ let mut (hash1, hash2) = (0,0);
+
+ for i in 0..16 {
+ hash1 = hash1*256 + hash[31-i].into();
+ hash2 = hash2*256 + hash[15-i].into();
+ }
+```
+
+Укажите `hash1` и `hash2` как изменяемые переменные и запишите в них хэш побайтово.
+
+```rust
+ (
+ ecrecover::ecrecover(pubKeyX, pubKeyY, signature, hash),
+```
+
+Это похоже на [`ecrecover` в Solidity](https://docs.soliditylang.org/en/v0.8.30/cheatsheet.html#mathematical-and-cryptographic-functions), с двумя важными отличиями:
+
+- Если подпись недействительна, вызов не проходит `assert`, и программа прерывается.
+- Хотя публичный ключ можно восстановить из подписи и хэша, это обработка, которую можно выполнить извне, и, следовательно, не стоит делать внутри доказательства с нулевым разглашением. Если кто-то попытается обмануть нас здесь, проверка подписи не удастся.
+
+```rust
+ hash1,
+ hash2
+ )
+}
+
+fn main(
+ accounts: [Account; ACCOUNT_NUMBER],
+ message: str,
+ pubKeyX: [u8; 32],
+ pubKeyY: [u8; 32],
+ signature: [u8; 64],
+ ) -> pub (
+ Field, // Hash of old accounts array
+ Field, // Hash of new accounts array
+ Field, // First 16 bytes of message hash
+ Field, // Last 16 bytes of message hash
+ )
+```
+
+Наконец, мы добрались до функции `main`. Нам нужно доказать, что у нас есть транзакция, которая действительным образом изменяет хэш аккаунтов со старого значения на новое. Нам также нужно доказать, что у нее есть этот конкретный хэш транзакции, чтобы человек, который ее отправил, знал, что его транзакция была обработана.
+
+```rust
+{
+ let mut txn = readTransferTxn(message);
+```
+
+Нам нужно, чтобы `txn` был изменяемым, потому что мы не читаем адрес «от кого» из сообщения, мы читаем его из подписи.
+
+```rust
+ let (fromAddress, txnHash1, txnHash2) = signatureToAddressAndHash(
+ message,
+ pubKeyX,
+ pubKeyY,
+ signature);
+
+ txn.from = fromAddress;
+
+ let newAccounts = apply_transfer_txn(accounts, txn);
+
+ (
+ hash_accounts(accounts),
+ hash_accounts(newAccounts),
+ txnHash1,
+ txnHash2
+ )
+}
+```
+
+### Этап 2 — Добавление сервера {#stage-2}
+
+На втором этапе мы добавляем сервер, который получает и реализует транзакции перевода из браузера.
+
+Чтобы увидеть это в действии:
+
+1. Остановите Vite, если он запущен.
+
+2. Загрузите ветку, которая включает сервер, и убедитесь, что у вас есть все необходимые модули.
+
+ ```sh
+ git checkout 02-add-server
+ cd client
+ npm install
+ cd ../server
+ npm install
+ ```
+
+ Нет необходимости компилировать код Noir, он такой же, как код, который вы использовали для этапа 1.
+
+3. Запустите сервер.
+
+ ```sh
+ npm run start
+ ```
+
+4. В отдельном окне командной строки запустите Vite для обслуживания кода браузера.
+
+ ```sh
+ cd client
+ npm run dev
+ ```
+
+5. Перейдите к клиентскому коду по адресу [http://localhost:5173](http://localhost:5173).
+
+6. Прежде чем вы сможете выполнить транзакцию, вам нужно знать nonce, а также сумму, которую вы можете отправить. Чтобы получить эту информацию, нажмите **Update account data** (Обновить данные аккаунта) и подпишите сообщение.
+
+ Здесь у нас дилемма. С одной стороны, мы не хотим подписывать сообщение, которое может быть использовано повторно ([атака повторного воспроизведения](https://en.wikipedia.org/wiki/Replay_attack)), и именно поэтому нам в первую очередь нужен nonce. Однако у нас еще нет nonce. Решение состоит в том, чтобы выбрать nonce, который можно использовать только один раз и который у нас уже есть с обеих сторон, например, текущее время.
+
+ Проблема с этим решением в том, что время может быть не идеально синхронизировано. Поэтому вместо этого мы подписываем значение, которое меняется каждую минуту. Это означает, что наше окно уязвимости для атак повторного воспроизведения составляет не более одной минуты. Учитывая, что в производственной среде подписанный запрос будет защищен TLS, и что другая сторона туннеля — сервер — уже может раскрыть баланс и nonce (он должен их знать для работы), это приемлемый риск.
+
+7. Как только браузер получает обратно баланс и nonce, он показывает форму перевода. Выберите адрес назначения и сумму и нажмите **Transfer** (Перевести). Подпишите этот запрос.
+
+8. Чтобы увидеть перевод, либо **обновите данные аккаунта**, либо посмотрите в окне, где вы запускаете сервер. Сервер регистрирует состояние каждый раз, когда оно меняется.
+
+ ```
+ ori@CryptoDocGuy:~/x/250911-zk-bank/server$ npm run start
+
+ > server@1.0.0 start
+ > node --experimental-json-modules index.mjs
+
+ Listening on port 3000
+ Txn send 0x90F79bf6EB2c4f870365E785982E1f101E93b906 36000 finney (milliEth) 0 processed
+ New state:
+ 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 64000 (1)
+ 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 has 100000 (0)
+ 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has 100000 (0)
+ 0x90F79bf6EB2c4f870365E785982E1f101E93b906 has 136000 (0)
+ 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 has 100000 (0)
+ Txn send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 7200 finney (milliEth) 1 processed
+ New state:
+ 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 56800 (2)
+ 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 has 107200 (0)
+ 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has 100000 (0)
+ 0x90F79bf6EB2c4f870365E785982E1f101E93b906 has 136000 (0)
+ 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 has 100000 (0)
+ Txn send 0x90F79bf6EB2c4f870365E785982E1f101E93b906 3000 finney (milliEth) 2 processed
+ New state:
+ 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 53800 (3)
+ 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 has 107200 (0)
+ 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has 100000 (0)
+ 0x90F79bf6EB2c4f870365E785982E1f101E93b906 has 139000 (0)
+ 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 has 100000 (0)
+ ```
+
+#### `server/index.mjs` {#server-index-mjs-1}
+
+[Этот файл](https://github.com/qbzzt/250911-zk-bank/blob/02-add-server/server/index.mjs) содержит серверный процесс и взаимодействует с кодом Noir в [`main.nr`](https://github.com/qbzzt/250911-zk-bank/blob/02-add-server/server/noir/src/main.nr). Вот объяснение интересных частей.
+
+```js
+import { Noir } from '@noir-lang/noir_js'
+```
+
+Библиотека [noir.js](https://www.npmjs.com/package/@noir-lang/noir_js) взаимодействует между кодом JavaScript и кодом Noir.
+
+```js
+const circuit = JSON.parse(await fs.readFile("./noir/target/zkBank.json"))
+const noir = new Noir(circuit)
+```
+
+Загрузите арифметическую схему — скомпилированную программу Noir, которую мы создали на предыдущем этапе, — и подготовьтесь к ее выполнению.
+
+```js
+// Мы предоставляем информацию об аккаунте только в ответ на подписанный запрос
+const accountInformation = async signature => {
+ const fromAddress = await recoverAddress({
+ hash: hashMessage("Get account data " + Math.floor((new Date().getTime())/60000)),
+ signature
+ })
+```
+
+Для предоставления информации об аккаунте нам нужна только подпись. Причина в том, что мы уже знаем, каким будет сообщение, и, следовательно, хэш сообщения.
+
+```js
+const processMessage = async (message, signature) => {
+```
+
+Обработайте сообщение и выполните закодированную в нем транзакцию.
+
+```js
+ // Получить публичный ключ
+ const pubKey = await recoverPublicKey({
+ hash,
+ signature
+ })
+```
+
+Теперь, когда мы запускаем JavaScript на сервере, мы можем получить публичный ключ там, а не на клиенте.
+
+```js
+ let noirResult
+ try {
+ noirResult = await noir.execute({
+ message,
+ signature: signature.slice(2,-2).match(/.{2}/g).map(x => `0x${x}`),
+ pubKeyX,
+ pubKeyY,
+ accounts: Accounts
+ })
+```
+
+`noir.execute` запускает программу Noir. Параметры эквивалентны тем, что предоставлены в [`Prover.toml`](https://github.com/qbzzt/250911-zk-bank/blob/01-manual-zk/server/noir/Prover.toml). Обратите внимание, что длинные значения предоставляются как массив шестнадцатеричных строк (`["0x60", "0xA7"]`), а не как одно шестнадцатеричное значение (`0x60A7`), как это делает Viem.
+
+```js
+ } catch (err) {
+ console.log(`Noir error: ${err}`)
+ throw Error("Invalid transaction, not processed")
+ }
+```
+
+Если возникла ошибка, перехватите ее, а затем передайте упрощенную версию клиенту.
+
+```js
+ Accounts[fromAccountNumber].nonce++
+ Accounts[fromAccountNumber].balance -= amount
+ Accounts[toAccountNumber].balance += amount
+```
+
+Примените транзакцию. Мы уже сделали это в коде Noir, но здесь проще сделать это снова, чем извлекать результат оттуда.
+
+```js
+let Accounts = [
+ {
+ address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
+ balance: 5000,
+ nonce: 0,
+ },
+```
+
+Начальная структура `Accounts`.
+
+### Этап 3 — Смарт-контракты Ethereum {#stage-3}
+
+1. Остановите процессы сервера и клиента.
+
+2. Загрузите ветку со смарт-контрактами и убедитесь, что у вас есть все необходимые модули.
+
+ ```sh
+ git checkout 03-smart-contracts
+ cd client
+ npm install
+ cd ../server
+ npm install
+ ```
+
+3. Запустите `anvil` в отдельном окне командной строки.
+
+4. Сгенерируйте ключ верификации и верификатор Solidity, затем скопируйте код верификатора в проект Solidity.
+
+ ```sh
+ cd noir
+ bb write_vk -b ./target/zkBank.json -o ./target --oracle_hash keccak
+ bb write_solidity_verifier -k ./target/vk -o ./target/Verifier.sol
+ cp target/Verifier.sol ../../smart-contracts/src
+ ```
+
+5. Перейдите к смарт-контрактам и установите переменные среды для использования блокчейна `anvil`.
+
+ ```sh
+ cd ../../smart-contracts
+ export ETH_RPC_URL=http://localhost:8545
+ ETH_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+ ```
+
+6. Разверните `Verifier.sol` и сохраните адрес в переменной среды.
+
+ ```sh
+ VERIFIER_ADDRESS=`forge create src/Verifier.sol:HonkVerifier --private-key $ETH_PRIVATE_KEY --optimize --broadcast | awk '/Deployed to:/ {print $3}'`
+ echo $VERIFIER_ADDRESS
+ ```
+
+7. Разверните контракт `ZkBank`.
+
+ ```sh
+ ZKBANK_ADDRESS=`forge create ZkBank --private-key $ETH_PRIVATE_KEY --broadcast --constructor-args $VERIFIER_ADDRESS 0x199aa62af8c1d562a6ec96e66347bf3240ab2afb5d022c895e6bf6a5e617167b | awk '/Deployed to:/ {print $3}'`
+ echo $ZKBANK_ADDRESS
+ ```
+
+ Значение `0x199..67b` — это хэш Педерсена начального состояния `Accounts`. Если вы измените это начальное состояние в `server/index.mjs`, вы можете запустить транзакцию, чтобы увидеть начальный хэш, сообщаемый доказательством с нулевым разглашением.
+
+8. Запустите сервер.
+
+ ```sh
+ cd ../server
+ npm run start
+ ```
+
+9. Запустите клиент в другом окне командной строки.
+
+ ```sh
+ cd client
+ npm run dev
+ ```
+
+10. Выполните несколько транзакций.
+
+11. Чтобы убедиться, что состояние изменилось в блокчейне, перезапустите процесс сервера. Убедитесь, что `ZkBank` больше не принимает транзакции, потому что исходное значение хэша в транзакциях отличается от значения хэша, хранящегося в блокчейне.
+
+ Это ожидаемый тип ошибки.
+
+ ```
+ ori@CryptoDocGuy:~/x/250911-zk-bank/server$ npm run start
+
+ > server@1.0.0 start
+ > node --experimental-json-modules index.mjs
+
+ Listening on port 3000
+ Verification error: ContractFunctionExecutionError: The contract function "processTransaction" reverted with the following reason:
+ Wrong old state hash
+
+ Contract Call:
+ address: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
+ function: processTransaction(bytes _proof, bytes32[] _publicInputs)
+ args: (0x0000000000000000000000000000000000000000000000042ab5d6d1986846cf00000000000000000000000000000000000000000000000b75c020998797da7800000000000000000000000000000000000000000000000
+ ```
+
+#### `server/index.mjs` {#server-index-mjs-2}
+
+Изменения в этом файле в основном касаются создания фактического доказательства и его отправки в блокчейн.
+
+```js
+import { exec } from 'child_process'
+import util from 'util'
+
+const execPromise = util.promisify(exec)
+```
+
+Нам нужно использовать [пакет Barretenberg](https://github.com/AztecProtocol/aztec-packages/tree/next/barretenberg) для создания фактического доказательства для отправки в блокчейн. Мы можем использовать этот пакет либо через интерфейс командной строки (`bb`), либо с помощью [библиотеки JavaScript, `bb.js`](https://www.npmjs.com/package/@aztec/bb.js). Библиотека JavaScript работает намного медленнее, чем нативный код, поэтому мы используем [`exec`](https://nodejs.org/api/child_process.html#child_processexeccommand-options-callback) для использования командной строки.
+
+Обратите внимание, что если вы решите использовать `bb.js`, вам нужно будет использовать версию, совместимую с версией Noir, которую вы используете. На момент написания статьи текущая версия Noir (1.0.0-beta.11) использует `bb.js` версии 0.87.
+
+```js
+const zkBankAddress = process.env.ZKBANK_ADDRESS || "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
+```
+
+Адрес здесь — это тот, который вы получаете, начиная с чистого `anvil` и следуя приведенным выше инструкциям.
+
+```js
+const walletClient = createWalletClient({
+ chain: anvil,
+ transport: http(),
+ account: privateKeyToAccount("0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6")
+})
+```
+
+Этот приватный ключ — один из стандартных предварительно пополненных аккаунтов в `anvil`.
+
+```js
+const generateProof = async (witness, fileID) => {
+```
+
+Сгенерируйте доказательство с помощью исполняемого файла `bb`.
+
+```js
+ const fname = `witness-${fileID}.gz`
+ await fs.writeFile(fname, witness)
+```
+
+Запишите свидетельство в файл.
+
+```js
+ await execPromise(`bb prove -b ./noir/target/zkBank.json -w ${fname} -o ${fileID} --oracle_hash keccak --output_format fields`)
+```
+
+Фактически создайте доказательство. Этот шаг также создает файл с публичными переменными, но нам он не нужен. Мы уже получили эти переменные из `noir.execute`.
+
+```js
+ const proof = "0x" + JSON.parse(await fs.readFile(`./${fileID}/proof_fields.json`)).reduce((a,b) => a+b, "").replace(/0x/g, "")
+```
+
+Доказательство представляет собой JSON-массив значений `Field`, каждое из которых представлено шестнадцатеричным значением. Однако нам нужно отправить его в транзакции как одно значение `bytes`, которое Viem представляет большой шестнадцатеричной строкой. Здесь мы изменяем формат, объединяя все значения, удаляя все `0x` и затем добавляя один в конце.
+
+```js
+ await execPromise(`rm -r ${fname} ${fileID}`)
+
+ return proof
+}
+```
+
+Очистка и возврат доказательства.
+
+```js
+const processMessage = async (message, signature) => {
+ .
+ .
+ .
+
+ const publicFields = noirResult.returnValue.map(x=>'0x' + x.slice(2).padStart(64, "0"))
+```
+
+Публичные поля должны быть массивом 32-байтовых значений. Однако, поскольку нам нужно было разделить хэш транзакции на два значения `Field`, он отображается как 16-байтовое значение. Здесь мы добавляем нули, чтобы Viem понял, что это на самом деле 32 байта.
+
+```js
+ const proof = await generateProof(noirResult.witness, `${fromAddress}-${nonce}`)
+```
+
+Каждый адрес использует каждый nonce только один раз, поэтому мы можем использовать комбинацию `fromAddress` и `nonce` в качестве уникального идентификатора для файла свидетельства и выходного каталога.
+
+```js
+ try {
+ await zkBank.write.processTransaction([
+ proof, publicFields])
+ } catch (err) {
+ console.log(`Verification error: ${err}`)
+ throw Error("Can't verify the transaction onchain")
+ }
+ .
+ .
+ .
+}
+```
+
+Отправьте транзакцию в блокчейн.
+
+#### `smart-contracts/src/ZkBank.sol` {#smart-contracts-src-zkbank-sol}
+
+Это ончейн-код, который получает транзакцию.
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity >=0.8.21;
+
+import {HonkVerifier} from "./Verifier.sol";
+
+contract ZkBank {
+ HonkVerifier immutable myVerifier;
+ bytes32 currentStateHash;
+
+ constructor(address _verifierAddress, bytes32 _initialStateHash) {
+ currentStateHash = _initialStateHash;
+ myVerifier = HonkVerifier(_verifierAddress);
+ }
+```
+
+Ончейн-код должен отслеживать две переменные: верификатор (отдельный контракт, созданный `nargo`) и текущий хэш состояния.
+
+```solidity
+ event TransactionProcessed(
+ bytes32 indexed transactionHash,
+ bytes32 oldStateHash,
+ bytes32 newStateHash
+ );
+```
+
+Каждый раз, когда состояние изменяется, мы генерируем событие `TransactionProcessed`.
+
+```solidity
+ function processTransaction(
+ bytes calldata _proof,
+ bytes32[] calldata _publicFields
+ ) public {
+```
+
+Эта функция обрабатывает транзакции. Она получает доказательство (как `bytes`) и публичные входные данные (как массив `bytes32`) в формате, требуемом верификатором (для минимизации обработки в блокчейне и, следовательно, затрат на газ).
+
+```solidity
+ require(_publicInputs[0] == currentStateHash,
+ "Wrong old state hash");
+```
+
+Доказательство с нулевым разглашением должно доказывать, что транзакция изменяет наш текущий хэш на новый.
+
+```solidity
+ myVerifier.verify(_proof, _publicFields);
+```
+
+Вызовите контракт верификатора для проверки доказательства с нулевым разглашением. Этот шаг отменяет транзакцию, если доказательство с нулевым разглашением неверно.
+
+```solidity
+ currentStateHash = _publicFields[1];
+
+ emit TransactionProcessed(
+ _publicFields[2]<<128 | _publicFields[3],
+ _publicFields[0],
+ _publicFields[1]
+ );
+ }
+}
+```
+
+Если все в порядке, обновите хэш состояния до нового значения и сгенерируйте событие `TransactionProcessed`.
+
+## Злоупотребления со стороны централизованного компонента {#abuses}
+
+Информационная безопасность состоит из трех атрибутов:
+
+- _Конфиденциальность_ — пользователи не могут читать информацию, на чтение которой они не авторизованы.
+- _Целостность_ — информация не может быть изменена, кроме как авторизованными пользователями авторизованным способом.
+- _Доступность_ — авторизованные пользователи могут использовать систему.
+
+В этой системе целостность обеспечивается с помощью доказательств с нулевым разглашением. Доступность гораздо сложнее гарантировать, а конфиденциальность невозможна, потому что банк должен знать баланс каждого аккаунта и все транзакции. Невозможно помешать сущности, обладающей информацией, делиться этой информацией.
+
+Возможно, удастся создать действительно конфиденциальный банк с использованием [скрытых адресов](https://vitalik.eth.limo/general/2023/01/20/stealth.html), но это выходит за рамки данной статьи.
+
+### Ложная информация {#false-info}
+
+Один из способов, которым сервер может нарушить целостность, — это предоставление ложной информации при [запросе данных](https://github.com/qbzzt/250911-zk-bank/blob/03-smart-contracts/server/index.mjs#L278-L291).
+
+Чтобы решить эту проблему, мы можем написать вторую программу Noir, которая получает аккаунты в качестве приватного ввода и адрес, для которого запрашивается информация, в качестве публичного ввода. Выводом являются баланс и nonce этого адреса, а также хэш аккаунтов.
+
+Конечно, это доказательство не может быть проверено в блокчейне, потому что мы не хотим публиковать nonce и балансы в блокчейне. Однако оно может быть проверено клиентским кодом, работающим в браузере.
+
+### Принудительные транзакции {#forced-txns}
+
+Обычный механизм для обеспечения доступности и предотвращения цензуры на L2 — это [принудительные транзакции](https://docs.optimism.io/stack/transactions/forced-transaction). Но принудительные транзакции не сочетаются с доказательствами с нулевым разглашением. Сервер — единственная сущность, которая может проверять транзакции.
+
+Мы можем изменить `smart-contracts/src/ZkBank.sol`, чтобы он принимал принудительные транзакции и не позволял серверу изменять состояние до их обработки. Однако это открывает нас для простой атаки типа «отказ в обслуживании». Что если принудительная транзакция недействительна и поэтому ее невозможно обработать?
+
+Решение — иметь доказательство с нулевым разглашением того, что принудительная транзакция недействительна. Это дает серверу три варианта:
+
+- Обработать принудительную транзакцию, предоставив доказательство с нулевым разглашением того, что она была обработана, и новый хэш состояния.
+- Отклонить принудительную транзакцию и предоставить контракту доказательство с нулевым разглашением того, что транзакция недействительна (неизвестный адрес, неверный nonce или недостаточный баланс).
+- Игнорировать принудительную транзакцию. Невозможно заставить сервер действительно обработать транзакцию, но это означает, что вся система недоступна.
+
+#### Облигации доступности {#avail-bonds}
+
+В реальной реализации, вероятно, будет какой-то мотив прибыли для поддержания работы сервера. Мы можем усилить этот стимул, заставив сервер разместить облигацию доступности, которую любой может сжечь, если принудительная транзакция не будет обработана в течение определенного периода.
+
+### Неправильный код Noir {#bad-noir-code}
+
+Обычно, чтобы заставить людей доверять смарт-контракту, мы загружаем исходный код в [обозреватель блоков](https://eth.blockscout.com/address/0x7D16d2c4e96BCFC8f815E15b771aC847EcbDB48b?tab=contract). Однако в случае доказательств с нулевым разглашением этого недостаточно.
+
+`Verifier.sol` содержит ключ верификации, который является функцией программы Noir. Однако этот ключ не говорит нам, какой была программа Noir. Чтобы действительно иметь доверенное решение, вам нужно загрузить программу Noir (и версию, которая ее создала). В противном случае доказательства с нулевым разглашением могут отражать другую программу, с бэкдором.
+
+Пока обозреватели блоков не начнут позволять нам загружать и проверять программы Noir, вам следует делать это самостоятельно (предпочтительно в [IPFS](/developers/tutorials/ipfs-decentralized-ui/)). Тогда опытные пользователи смогут загрузить исходный код, скомпилировать его самостоятельно, создать `Verifier.sol` и убедиться, что он идентичен тому, что находится в блокчейне.
+
+## Заключение {#conclusion}
+
+Приложения типа Plasma требуют централизованного компонента для хранения информации. Это открывает потенциальные уязвимости, но взамен позволяет нам сохранять конфиденциальность способами, недоступными в самом блокчейне. С помощью доказательств с нулевым разглашением мы можем обеспечить целостность и, возможно, сделать экономически выгодным для того, кто управляет централизованным компонентом, поддерживать доступность.
+
+[Больше моих работ смотрите здесь](https://cryptodocguy.pro/).
+
+## Благодарности {#acknowledgements}
+
+- Джош Крайтс прочитал черновик этой статьи и помог мне с запутанным вопросом по Noir.
+
+Ответственность за любые оставшиеся ошибки лежит на мне.
diff --git a/public/content/translations/ru/developers/tutorials/calling-a-smart-contract-from-javascript/index.md b/public/content/translations/ru/developers/tutorials/calling-a-smart-contract-from-javascript/index.md
new file mode 100644
index 00000000000..be71da99702
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/calling-a-smart-contract-from-javascript/index.md
@@ -0,0 +1,131 @@
+---
+title: "Вызов смарт-контракта из JavaScript"
+description: "Как вызвать функцию смарт-контракта из JavaScript на примере токена Dai"
+author: jdourlens
+tags: [ "транзакции", "интерфейс", "JavaScript", "web3.js" ]
+skill: beginner
+lang: ru
+published: 2020-04-19
+source: EthereumDev
+sourceUrl: https://ethereumdev.io/calling-a-smart-contract-from-javascript/
+address: "0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE"
+---
+
+В этом руководстве мы рассмотрим, как вызвать функцию [смарт-контракта](/developers/docs/smart-contracts/) из JavaScript. Сначала мы прочитаем состояние смарт-контракта (например, баланс держателя ERC20), а затем изменим состояние блокчейна, выполнив перевод токенов. Вы уже должны быть знакомы с [настройкой JS-среды для взаимодействия с блокчейном](/developers/tutorials/set-up-web3js-to-use-ethereum-in-javascript/).
+
+В этом примере мы будем работать с токеном DAI, для целей тестирования мы сделаем форк блокчейна с помощью ganache-cli и разблокируем адрес, на котором уже есть много DAI:
+
+```bash
+ganache-cli -f https://mainnet.infura.io/v3/[ВАШ КЛЮЧ INFURA] -d -i 66 1 --unlock 0x4d10ae710Bd8D1C31bd7465c8CBC3add6F279E81
+```
+
+Для взаимодействия со смарт-контрактом нам понадобятся его адрес и ABI:
+
+```js
+const ERC20TransferABI = [
+ {
+ constant: false,
+ inputs: [
+ {
+ name: "_to",
+ type: "address",
+ },
+ {
+ name: "_value",
+ type: "uint256",
+ },
+ ],
+ name: "transfer",
+ outputs: [
+ {
+ name: "",
+ type: "bool",
+ },
+ ],
+ payable: false,
+ stateMutability: "nonpayable",
+ type: "function",
+ },
+ {
+ constant: true,
+ inputs: [
+ {
+ name: "_owner",
+ type: "address",
+ },
+ ],
+ name: "balanceOf",
+ outputs: [
+ {
+ name: "balance",
+ type: "uint256",
+ },
+ ],
+ payable: false,
+ stateMutability: "view",
+ type: "function",
+ },
+]
+
+const DAI_ADDRESS = "0x6b175474e89094c44da98b954eedeac495271d0f"
+```
+
+Для этого проекта мы урезали полный ABI ERC20, оставив только функции `balanceOf` и `transfer`, но вы можете найти [полный ABI ERC20 здесь](https://ethereumdev.io/abi-for-erc20-contract-on-ethereum/).
+
+Затем нам нужно создать экземпляр нашего смарт-контракта:
+
+```js
+const web3 = new Web3("http://localhost:8545")
+
+const daiToken = new web3.eth.Contract(ERC20TransferABI, DAI_ADDRESS)
+```
+
+Мы также настроим два адреса:
+
+- тот, который получит перевод, и
+- тот, который мы уже разблокировали и с которого будем отправлять:
+
+```js
+const senderAddress = "0x4d10ae710Bd8D1C31bd7465c8CBC3add6F279E81"
+const receiverAddress = "0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE"
+```
+
+В следующей части мы вызовем функцию `balanceOf`, чтобы получить текущее количество токенов, которыми владеют оба адреса.
+
+## Вызов: чтение значения из смарт-контракта {#call-reading-value-from-a-smart-contract}
+
+В первом примере будет вызван «константный» метод, и его метод смарт-контракта будет выполнен в EVM без отправки какой-либо транзакции. Для этого мы прочитаем баланс ERC20 адреса. [Прочтите нашу статью о токенах ERC20](/developers/tutorials/understand-the-erc-20-token-smart-contract/).
+
+Вы можете получить доступ к методам созданного экземпляра смарт-контракта, для которого вы предоставили ABI, следующим образом: `yourContract.methods.methodname`. Используя функцию `call`, вы получите результат выполнения функции.
+
+```js
+daiToken.methods.balanceOf(senderAddress).call(function (err, res) {
+ if (err) {
+ console.log("Произошла ошибка", err)
+ return
+ }
+ console.log("Баланс: ", res)
+})
+```
+
+Помните, что DAI ERC20 имеет 18 знаков после запятой, что означает, что вам нужно убрать 18 нулей, чтобы получить правильную сумму. Значения uint256 возвращаются в виде строк, так как JavaScript не обрабатывает большие числовые значения. Если вы не уверены, [как работать с большими числами в JS, ознакомьтесь с нашим руководством по bignumber.js](https://ethereumdev.io/how-to-deal-with-big-numbers-in-javascript/).
+
+## Отправка: отправка транзакции в функцию смарт-контракта {#send-sending-a-transaction-to-a-smart-contract-function}
+
+Во втором примере мы вызовем функцию transfer смарт-контракта DAI, чтобы отправить 10 DAI на наш второй адрес. Функция transfer принимает два параметра: адрес получателя и сумму токенов для перевода:
+
+```js
+daiToken.methods
+ .transfer(receiverAddress, "100000000000000000000")
+ .send({ from: senderAddress }, function (err, res) {
+ if (err) {
+ console.log("Произошла ошибка", err)
+ return
+ }
+ console.log("Хэш транзакции: " + res)
+ })
+```
+
+Функция call возвращает хэш транзакции, которая будет включена в блокчейн. В Ethereum хэши транзакций предсказуемы — так мы можем получить хэш транзакции до ее выполнения ([узнайте, как рассчитываются хэши, здесь](https://ethereum.stackexchange.com/questions/45648/how-to-calculate-the-assigned-txhash-of-a-transaction)).
+
+Поскольку функция только отправляет транзакцию в блокчейн, мы не можем увидеть результат, пока не узнаем, когда она будет добыта и включена в блокчейн. В следующем руководстве мы узнаем, [как дождаться выполнения транзакции в блокчейне, зная ее хэш](https://ethereumdev.io/waiting-for-a-transaction-to-be-mined-on-ethereum-with-js/).
diff --git a/public/content/translations/ru/developers/tutorials/creating-a-wagmi-ui-for-your-contract/index.md b/public/content/translations/ru/developers/tutorials/creating-a-wagmi-ui-for-your-contract/index.md
new file mode 100644
index 00000000000..6189b78b58d
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/creating-a-wagmi-ui-for-your-contract/index.md
@@ -0,0 +1,585 @@
+---
+title: "Создание пользовательского интерфейса для вашего контракта"
+description: "Используя современные компоненты, такие как TypeScript, React, Vite и Wagmi, мы рассмотрим современный, но минимальный пользовательский интерфейс и научимся подключать кошелек к пользовательскому интерфейсу, вызывать смарт-контракт для чтения информации, отправлять транзакцию в смарт-контракт и отслеживать события со смарт-контракта для выявления изменений."
+author: Ori Pomerantz
+tags: [ "typescript", "react", "vite", "wagmi", "интерфейс" ]
+skill: beginner
+published: 2023-11-01
+lang: ru
+sidebarDepth: 3
+---
+
+Вы нашли функцию, которая нам нужна в экосистеме Ethereum. Вы написали смарт-контракты для ее реализации и, возможно, даже какой-то связанный с этим код, который выполняется вне сети. Это здорово! К сожалению, без пользовательского интерфейса у вас не будет пользователей, а в последний раз, когда вы писали веб-сайт, люди пользовались модемами с коммутируемым доступом, а JavaScript был в новинку.
+
+Эта статья для вас. Я предполагаю, что вы знаете программирование и, возможно, немного JavaScript и HTML, но ваши навыки работы с пользовательским интерфейсом устарели. Вместе мы рассмотрим простое современное приложение, чтобы вы увидели, как это делается в наши дни.
+
+## Почему это важно {#why-important}
+
+Теоретически вы могли бы просто позволить людям использовать [Etherscan](https://holesky.etherscan.io/address/0x432d810484add7454ddb3b5311f0ac2e95cecea8#writeContract) или [Blockscout](https://eth-holesky.blockscout.com/address/0x432d810484AdD7454ddb3b5311f0Ac2E95CeceA8?tab=write_contract) для взаимодействия с вашими контрактами. Это будет отлично для опытных пользователей Ethereum. Но мы пытаемся обслужить [еще миллиард человек](https://blog.ethereum.org/2021/05/07/ethereum-for-the-next-billion). Этого не произойдет без отличного пользовательского опыта, а дружественный пользовательский интерфейс — большая его часть.
+
+## Приложение Greeter {#greeter-app}
+
+Существует много теории о том, как работает современный пользовательский интерфейс, и [много хороших сайтов](https://react.dev/learn/thinking-in-react), [которые это объясняют](https://wagmi.sh/core/getting-started). Вместо того чтобы повторять прекрасную работу, проделанную на этих сайтах, я предположу, что вы предпочитаете учиться на практике и начнете с приложения, с которым можно поиграть. Вам все еще нужна теория, чтобы все сделать, и мы до нее доберемся — мы просто будем разбирать исходный файл за исходным файлом и обсуждать все по мере их появления.
+
+### Установка {#installation}
+
+1. При необходимости добавьте [блокчейн Holesky](https://chainlist.org/?search=holesky&testnets=true) в свой кошелек и [получите тестовые ETH](https://www.holeskyfaucet.io/).
+
+2. Клонируйте репозиторий github.
+
+ ```sh
+ git clone https://github.com/qbzzt/20230801-modern-ui.git
+ ```
+
+3. Установить нужные пакеты.
+
+ ```sh
+ cd 20230801-modern-ui
+ pnpm install
+ ```
+
+4. Запустите приложение.
+
+ ```sh
+ pnpm dev
+ ```
+
+5. Перейдите по URL-адресу, указанному в приложении. В большинстве случаев это [http://localhost:5173/](http://localhost:5173/).
+
+6. Вы можете увидеть исходный код контракта, немного измененную версию Greeter от Hardhat, [в обозревателе блокчейна](https://eth-holesky.blockscout.com/address/0x432d810484AdD7454ddb3b5311f0Ac2E95CeceA8?tab=contract).
+
+### Обзор файлов {#file-walk-through}
+
+#### `index.html` {#index-html}
+
+Этот файл является стандартным шаблоном HTML, за исключением этой строки, которая импортирует файл скрипта.
+
+```html
+
+```
+
+#### `src/main.tsx` {#main-tsx}
+
+Расширение файла говорит нам, что этот файл является [компонентом React](https://www.w3schools.com/react/react_components.asp), написанным на [TypeScript](https://www.typescriptlang.org/), расширении JavaScript, которое поддерживает [проверку типов](https://en.wikipedia.org/wiki/Type_system#Type_checking). TypeScript компилируется в JavaScript, поэтому мы можем использовать его для выполнения на стороне клиента.
+
+```tsx
+import '@rainbow-me/rainbowkit/styles.css'
+import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
+import * as React from 'react'
+import * as ReactDOM from 'react-dom/client'
+import { WagmiConfig } from 'wagmi'
+import { chains, config } from './wagmi'
+```
+
+Импортируйте необходимый код библиотеки.
+
+```tsx
+import { App } from './App'
+```
+
+Импортируйте компонент React, который реализует приложение (см. ниже).
+
+```tsx
+ReactDOM.createRoot(document.getElementById('root')!).render(
+```
+
+Создайте корневой компонент React. Параметр `render` — это [JSX](https://www.w3schools.com/react/react_jsx.asp), язык-расширение, который использует как HTML, так и JavaScript/TypeScript. Восклицательный знак здесь говорит компоненту TypeScript: "вы не знаете, что `document.getElementById('root')` будет допустимым параметром для `ReactDOM.createRoot`, но не волнуйтесь - я разработчик и я говорю вам, что он будет".
+
+```tsx
+
+```
+
+Приложение находится внутри [компонента `React.StrictMode`](https://react.dev/reference/react/StrictMode). Этот компонент указывает библиотеке React вставлять дополнительные проверки для отладки, что полезно во время разработки.
+
+```tsx
+
+```
+
+Приложение также находится внутри [компонента `WagmiConfig`](https://wagmi.sh/react/api/WagmiProvider). [Библиотека wagmi (we are going to make it)](https://wagmi.sh/) соединяет определения пользовательского интерфейса React с [библиотекой viem](https://viem.sh/) для написания децентрализованного приложения Ethereum.
+
+```tsx
+
+```
+
+И, наконец, [компонент `RainbowKitProvider`](https://www.rainbowkit.com/). Этот компонент обрабатывает вход в систему и связь между кошельком и приложением.
+
+```tsx
+
+```
+
+Теперь у нас может быть компонент для приложения, который фактически реализует пользовательский интерфейс. Символ `/>` в конце компонента говорит React, что этот компонент не содержит никаких определений внутри себя, согласно стандарту XML.
+
+```tsx
+
+
+ ,
+)
+```
+
+Конечно, мы должны закрыть другие компоненты.
+
+#### `src/App.tsx` {#app-tsx}
+
+```tsx
+import { ConnectButton } from '@rainbow-me/rainbowkit'
+import { useAccount } from 'wagmi'
+import { Greeter } from './components/Greeter'
+
+export function App() {
+```
+
+Это стандартный способ создания компонента React — определить функцию, которая вызывается каждый раз, когда ее нужно отрисовать. Эта функция обычно содержит в начале некоторый код TypeScript или JavaScript, за которым следует оператор `return`, возвращающий код JSX.
+
+```tsx
+ const { isConnected } = useAccount()
+```
+
+Здесь мы используем [`useAccount`](https://wagmi.sh/react/api/hooks/useAccount), чтобы проверить, подключены ли мы к блокчейну через кошелек или нет.
+
+По соглашению, в React функции, называемые `use...`, являются [хуками](https://www.w3schools.com/react/react_hooks.asp), которые возвращают какие-либо данные. Когда вы используете такие хуки, ваш компонент не только получает данные, но и при изменении этих данных компонент повторно отрисовывается с обновленной информацией.
+
+```tsx
+ return (
+ <>
+```
+
+JSX компонента React _должен_ возвращать один компонент. Когда у нас несколько компонентов, и нет ничего, что оборачивает их "естественным образом", мы используем пустой компонент (`<> ...` >`) чтобы сделать из них один компонент.
+
+```tsx
+
Greeter
+
+```
+
+Мы получаем [компонент `ConnectButton`](https://www.rainbowkit.com/docs/connect-button) из RainbowKit. Когда мы не подключены, он предоставляет нам кнопку `Connect Wallet`, которая открывает модальное окно, объясняющее, что такое кошельки, и позволяющее выбрать, какой из них использовать. Когда мы подключены, он отображает используемый нами блокчейн, адрес нашего аккаунта и баланс ETH. Мы можем использовать эти дисплеи для переключения сети или для отключения.
+
+```tsx
+ {isConnected && (
+```
+
+Когда нам нужно вставить фактический JavaScript (или TypeScript, который будет скомпилирован в JavaScript) в JSX, мы используем скобки (`{}`).
+
+Синтаксис `a && b` является сокращением для [`a ?` b : a`](https://www.w3schools.com/react/react_es6_ternary.asp). То есть, если `a`истинно, оно вычисляется как`b`, а в противном случае — как `a`(которое может быть`false`, `0` и т.д.). Это простой способ сообщить React, что компонент должен отображаться только при выполнении определенного условия.
+
+В этом случае мы хотим показывать пользователю `Greeter` только если пользователь подключен к блокчейну.
+
+```tsx
+
+ )}
+ >
+ )
+}
+```
+
+#### `src/components/Greeter.tsx` {#greeter-tsx}
+
+Этот файл содержит большую часть функциональности пользовательского интерфейса. Он включает определения, которые обычно находятся в нескольких файлах, но поскольку это учебное пособие, программа оптимизирована для простоты понимания с первого раза, а не для производительности или простоты обслуживания.
+
+```tsx
+import { useState, ChangeEventHandler } from 'react'
+import { useNetwork,
+ useReadContract,
+ usePrepareContractWrite,
+ useContractWrite,
+ useContractEvent
+ } from 'wagmi'
+```
+
+Мы используем эти функции библиотеки. Опять же, они объясняются ниже, там, где они используются.
+
+```tsx
+import { AddressType } from 'abitype'
+```
+
+[Библиотека `abitype`](https://abitype.dev/) предоставляет нам определения TypeScript для различных типов данных Ethereum, таких как [`AddressType`](https://abitype.dev/config#addresstype).
+
+```tsx
+let greeterABI = [
+ .
+ .
+ .
+] as const // greeterABI
+```
+
+ABI для контракта `Greeter`.
+Если вы разрабатываете контракты и пользовательский интерфейс одновременно, вы обычно помещаете их в один репозиторий и используете ABI, сгенерированный компилятором Solidity, как файл в вашем приложении. Однако здесь это не обязательно, потому что контракт уже разработан и меняться не будет.
+
+```tsx
+type AddressPerBlockchainType = {
+ [key: number]: AddressType
+}
+```
+
+TypeScript строго типизирован. Мы используем это определение, чтобы указать адрес, по которому контракт `Greeter` развернут в разных сетях. Ключ — это число (chainId), а значение — `AddressType` (адрес).
+
+```tsx
+const contractAddrs: AddressPerBlockchainType = {
+ // Holesky
+ 17000: '0x432d810484AdD7454ddb3b5311f0Ac2E95CeceA8',
+
+ // Sepolia
+ 11155111: '0x7143d5c190F048C8d19fe325b748b081903E3BF0'
+}
+```
+
+Адрес контракта в двух поддерживаемых сетях: [Holesky](https://eth-holesky.blockscout.com/address/0x432d810484AdD7454ddb3b5311f0Ac2E95CeceA8?tab=contact_code) и [Sepolia](https://eth-sepolia.blockscout.com/address/0x7143d5c190F048C8d19fe325b748b081903E3BF0?tab=contact_code).
+
+Примечание: на самом деле есть третье определение, для Redstone Holesky, оно будет объяснено ниже.
+
+```tsx
+type ShowObjectAttrsType = {
+ name: string,
+ object: any
+}
+```
+
+Этот тип используется в качестве параметра для компонента `ShowObject` (объяснение будет позже). Он включает имя объекта и его значение, которые отображаются в целях отладки.
+
+```tsx
+type ShowGreetingAttrsType = {
+ greeting: string | undefined
+}
+```
+
+В любой момент времени мы можем либо знать, что такое приветствие (потому что мы прочитали его из блокчейна), либо не знать (потому что мы его еще не получили). Поэтому полезно иметь тип, который может быть либо строкой, либо ничем.
+
+##### Компонент `Greeter` {#greeter-component}
+
+```tsx
+const Greeter = () => {
+```
+
+Наконец, мы переходим к определению компонента.
+
+```tsx
+ const { chain } = useNetwork()
+```
+
+Информация о сети, которую мы используем, предоставленная [wagmi](https://wagmi.sh/react/hooks/useNetwork).
+Поскольку это хук (`use...`), каждый раз, когда эта информация меняется, компонент перерисовывается.
+
+```tsx
+ const greeterAddr = chain && contractAddrs[chain.id]
+```
+
+Адрес контракта Greeter, который зависит от сети (и который равен `undefined`, если у нас нет информации о сети или мы находимся в сети без этого контракта).
+
+```tsx
+ const readResults = useReadContract({
+ address: greeterAddr,
+ abi: greeterABI,
+ functionName: "greet" , // Нет аргументов
+ watch: true
+ })
+```
+
+[Хук `useReadContract`](https://wagmi.sh/react/api/hooks/useReadContract) читает информацию из контракта. Вы можете точно увидеть, какую информацию он возвращает, раскрыв `readResults` в пользовательском интерфейсе. В этом случае мы хотим, чтобы он продолжал отслеживать, чтобы мы были проинформированы, когда приветствие изменится.
+
+**Примечание:** Мы могли бы прослушивать [события `setGreeting`](https://eth-holesky.blockscout.com/address/0x432d810484AdD7454ddb3b5311f0Ac2E95CeceA8?tab=logs), чтобы знать, когда меняется приветствие, и обновлять его таким образом. Однако, хотя это может быть более эффективно, это применимо не во всех случаях. Когда пользователь переключается на другую сеть, приветствие также меняется, но это изменение не сопровождается событием. Мы могли бы иметь одну часть кода, прослушивающую события, и другую для определения изменений сети, но это было бы сложнее, чем просто установить [параметр `watch`](https://wagmi.sh/react/api/hooks/useReadContract#watch-optional).
+
+```tsx
+ const [ newGreeting, setNewGreeting ] = useState("")
+```
+
+[Хук `useState` из React](https://www.w3schools.com/react/react_usestate.asp) позволяет нам указать переменную состояния, значение которой сохраняется от одной отрисовки компонента к другой. Начальное значение — это параметр, в данном случае пустая строка.
+
+Хук `useState` возвращает список с двумя значениями:
+
+1. Текущее значение переменной состояния.
+2. Функция для изменения переменной состояния при необходимости. Поскольку это хук, каждый раз при его вызове компонент отрисовывается заново.
+
+В этом случае мы используем переменную состояния для нового приветствия, которое хочет установить пользователь.
+
+```tsx
+ const greetingChange : ChangeEventHandler = (evt) =>
+ setNewGreeting(evt.target.value)
+```
+
+Это обработчик события изменения поля ввода нового приветствия. Тип [`ChangeEventHandler`](https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/forms_and_events/) указывает, что это обработчик изменения значения элемента ввода HTML. Часть `` используется, потому что это [обобщенный тип](https://www.w3schools.com/typescript/typescript_basic_generics.php).
+
+```tsx
+ const preparedTx = usePrepareContractWrite({
+ address: greeterAddr,
+ abi: greeterABI,
+ functionName: 'setGreeting',
+ args: [ newGreeting ]
+ })
+ const workingTx = useContractWrite(preparedTx.config)
+```
+
+Это процесс отправки транзакции в блокчейн с точки зрения клиента:
+
+1. Отправьте транзакцию узлу в блокчейне с помощью [`eth_estimateGas`](https://docs.alchemy.com/reference/eth-estimategas).
+2. Дождитесь ответа от узла.
+3. Когда ответ получен, попросите пользователя подписать транзакцию через кошелек. Этот шаг _должен_ произойти после получения ответа от узла, потому что пользователю показывается стоимость газа транзакции перед ее подписанием.
+4. Дождитесь одобрения пользователя.
+5. Отправьте транзакцию еще раз, на этот раз используя [`eth_sendRawTransaction`](https://docs.alchemy.com/reference/eth-sendrawtransaction).
+
+Шаг 2, скорее всего, займет ощутимое количество времени, в течение которого пользователи будут задаваться вопросом, действительно ли их команда была получена пользовательским интерфейсом и почему им еще не предлагают подписать транзакцию. Это приводит к плохому пользовательскому опыту (UX).
+
+Решение — использовать [хуки подготовки](https://wagmi.sh/react/prepare-hooks). Каждый раз, когда меняется параметр, немедленно отправляйте узлу запрос `eth_estimateGas`. Затем, когда пользователь действительно хочет отправить транзакцию (в данном случае, нажав **Обновить приветствие**), стоимость газа известна, и пользователь может сразу увидеть страницу кошелька.
+
+```tsx
+ return (
+```
+
+Теперь мы наконец можем создать фактический HTML для возврата.
+
+```tsx
+ <>
+
Greeter
+ {
+ !readResults.isError && !readResults.isLoading &&
+
+ }
+
+```
+
+Создайте компонент `ShowGreeting` (описан ниже), но только если приветствие было успешно прочитано из блокчейна.
+
+```tsx
+
+```
+
+Это поле для ввода текста, где пользователь может установить новое приветствие. Каждый раз, когда пользователь нажимает клавишу, мы вызываем `greetingChange`, который вызывает `setNewGreeting`. Поскольку `setNewGreeting` происходит из хука `useState`, это заставляет компонент `Greeter` отрисовываться снова. Это означает, что:
+
+- Нам нужно указать `value`, чтобы сохранить значение нового приветствия, иначе оно вернется к значению по умолчанию — пустой строке.
+- `usePrepareContractWrite` вызывается каждый раз, когда `newGreeting` меняется, что означает, что в подготовленной транзакции всегда будет самое последнее `newGreeting`.
+
+```tsx
+
+```
+
+Если `workingTx.write` отсутствует, значит, мы все еще ждем информацию, необходимую для отправки обновления приветствия, поэтому кнопка отключена. Если значение `workingTx.write` есть, то это функция, которую нужно вызвать для отправки транзакции.
+
+```tsx
+
+
+
+
+ >
+ )
+}
+```
+
+Наконец, чтобы помочь вам увидеть, что мы делаем, покажем три объекта, которые мы используем:
+
+- `readResults`
+- `preparedTx`
+- `workingTx`
+
+##### Компонент `ShowGreeting` {#showgreeting-component}
+
+Этот компонент показывает
+
+```tsx
+const ShowGreeting = (attrs : ShowGreetingAttrsType) => {
+```
+
+Функция компонента получает параметр со всеми атрибутами компонента.
+
+```tsx
+ return {attrs.greeting}
+}
+```
+
+##### Компонент `ShowObject` {#showobject-component}
+
+В информационных целях мы используем компонент `ShowObject` для отображения важных объектов (`readResults` для чтения приветствия и `preparedTx` и `workingTx` для создаваемых нами транзакций).
+
+```tsx
+const ShowObject = (attrs: ShowObjectAttrsType ) => {
+ const keys = Object.keys(attrs.object)
+ const funs = keys.filter(k => typeof attrs.object[k] == "function")
+ return <>
+
+```
+
+Мы не хотим загромождать пользовательский интерфейс всей информацией, поэтому, чтобы можно было просматривать или скрывать ее, мы используем тег [`details`](https://www.w3schools.com/tags/tag_details.asp).
+
+```tsx
+ {attrs.name}
+
+ {JSON.stringify(attrs.object, null, 2)}
+```
+
+Большинство полей отображаются с помощью [`JSON.stringify`](https://www.w3schools.com/js/js_json_stringify.asp).
+
+```tsx
+
+ { funs.length > 0 &&
+ <>
+ Функции:
+
+```
+
+Исключение составляют функции, которые не являются частью [стандарта JSON](https://www.json.org/json-en.html), поэтому их нужно отображать отдельно.
+
+```tsx
+ {funs.map((f, i) =>
+```
+
+Внутри JSX код внутри `{` фигурных скобок `}` интерпретируется как JavaScript. Затем код внутри `(` круглых скобок `)` снова интерпретируется как JSX.
+
+```tsx
+ (
{f}
)
+ )}
+```
+
+React требует, чтобы теги в [дереве DOM](https://www.w3schools.com/js/js_htmldom.asp) имели уникальные идентификаторы. Это означает, что дочерние элементы одного и того же тега (в данном случае [неупорядоченного списка](https://www.w3schools.com/tags/tag_ul.asp)) должны иметь разные атрибуты `key`.
+
+```tsx
+
+ >
+ }
+
+ >
+}
+```
+
+Закройте различные HTML-теги.
+
+##### Финальный `export` {#the-final-export}
+
+```tsx
+export { Greeter }
+```
+
+Компонент `Greeter` — это тот, который нам нужно экспортировать для приложения.
+
+#### `src/wagmi.ts` {#wagmi-ts}
+
+Наконец, различные определения, связанные с WAGMI, находятся в `src/wagmi.ts`. Я не буду здесь все объяснять, потому что большая часть — это шаблонный код, который вам вряд ли понадобится менять.
+
+Код здесь не совсем такой же, как [на github](https://github.com/qbzzt/20230801-modern-ui/blob/main/src/wagmi.ts), потому что позже в статье мы добавляем еще одну сеть ([Redstone Holesky](https://redstone.xyz/docs/network-info)).
+
+```ts
+import { getDefaultWallets } from '@rainbow-me/rainbowkit'
+import { configureChains, createConfig } from 'wagmi'
+import { holesky, sepolia } from 'wagmi/chains'
+```
+
+Импортируйте блокчейны, которые поддерживает приложение. Вы можете увидеть список поддерживаемых сетей [на GitHub viem](https://github.com/wagmi-dev/viem/tree/main/src/chains/definitions).
+
+```ts
+import { publicProvider } from 'wagmi/providers/public'
+
+const walletConnectProjectId = 'c96e690bb92b6311e8e9b2a6a22df575'
+```
+
+Чтобы использовать [WalletConnect](https://walletconnect.com/), вам нужен идентификатор проекта для вашего приложения. Вы можете получить его на [cloud.walletconnect.com](https://cloud.walletconnect.com/sign-in).
+
+```ts
+const { chains, publicClient, webSocketPublicClient } = configureChains(
+ [ holesky, sepolia ],
+ [
+ publicProvider(),
+ ],
+)
+
+const { connectors } = getDefaultWallets({
+ appName: 'My wagmi + RainbowKit App',
+ chains,
+ projectId: walletConnectProjectId,
+})
+
+export const config = createConfig({
+ autoConnect: true,
+ connectors,
+ publicClient,
+ webSocketPublicClient,
+})
+
+export { chains }
+```
+
+### Добавление другого блокчейна {#add-blockchain}
+
+В наши дни существует множество [решений для масштабирования L2](/layer-2/), и вы, возможно, захотите поддержать некоторые, которые viem еще не поддерживает. Для этого вы изменяете `src/wagmi.ts`. Эти инструкции объясняют, как добавить [Redstone Holesky](https://redstone.xyz/docs/network-info).
+
+1. Импортируйте тип `defineChain` из viem.
+
+ ```ts
+ import { defineChain } from 'viem'
+ ```
+
+2. Добавьте определение сети.
+
+ ```ts
+ const redstoneHolesky = defineChain({
+ id: 17_001,
+ name: 'Redstone Holesky',
+ network: 'redstone-holesky',
+ nativeCurrency: {
+ decimals: 18,
+ name: 'Ether',
+ symbol: 'ETH',
+ },
+ rpcUrls: {
+ default: {
+ http: ['https://rpc.holesky.redstone.xyz'],
+ webSocket: ['wss://rpc.holesky.redstone.xyz/ws'],
+ },
+ public: {
+ http: ['https://rpc.holesky.redstone.xyz'],
+ webSocket: ['wss://rpc.holesky.redstone.xyz/ws'],
+ },
+ },
+ blockExplorers: {
+ default: { name: 'Explorer', url: 'https://explorer.holesky.redstone.xyz' },
+ },
+ })
+ ```
+
+3. Добавьте новую сеть в вызов `configureChains`.
+
+ ```ts
+ const { chains, publicClient, webSocketPublicClient } = configureChains(
+ [ holesky, sepolia, redstoneHolesky ],
+ [ publicProvider(), ],
+ )
+ ```
+
+4. Убедитесь, что приложение знает адрес ваших контрактов в новой сети. В этом случае мы изменяем `src/components/Greeter.tsx`:
+
+ ```ts
+ const contractAddrs : AddressPerBlockchainType = {
+ // Holesky
+ 17000: '0x432d810484AdD7454ddb3b5311f0Ac2E95CeceA8',
+
+ // Redstone Holesky
+ 17001: '0x4919517f82a1B89a32392E1BF72ec827ba9986D3',
+
+ // Sepolia
+ 11155111: '0x7143d5c190F048C8d19fe325b748b081903E3BF0'
+ }
+ ```
+
+## Заключение {#conclusion}
+
+Конечно, вас не особо волнует предоставление пользовательского интерфейса для `Greeter`. Вы хотите создать пользовательский интерфейс для своих собственных контрактов. Чтобы создать собственное приложение, выполните следующие шаги:
+
+1. Укажите, что нужно создать приложение wagmi.
+
+ ```sh copy
+ pnpm create wagmi
+ ```
+
+2. Назовите приложение.
+
+3. Выберите фреймворк **React**.
+
+4. Выберите вариант **Vite**.
+
+5. Вы можете [добавить Rainbow kit](https://www.rainbowkit.com/docs/installation#manual-setup).
+
+Теперь идите и сделайте свои контракты пригодными для использования во всем мире.
+
+[Больше моих работ смотрите здесь](https://cryptodocguy.pro/).
+
diff --git a/public/content/translations/ru/developers/tutorials/deploying-your-first-smart-contract/index.md b/public/content/translations/ru/developers/tutorials/deploying-your-first-smart-contract/index.md
new file mode 100644
index 00000000000..7c657b2be6f
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/deploying-your-first-smart-contract/index.md
@@ -0,0 +1,101 @@
+---
+title: "Развертывание вашего первого смарт-контракта"
+description: "Введение в развертывание вашего первого смарт-контракта в тестовой сети Ethereum"
+author: "jdourlens"
+tags:
+ [
+ "Умные контракты",
+ "remix",
+ "твердость",
+ "развертывание"
+ ]
+skill: beginner
+lang: ru
+published: 2020-04-03
+source: EthereumDev
+sourceUrl: https://ethereumdev.io/deploying-your-first-smart-contract/
+address: "0x19dE91Af973F404EDF5B4c093983a7c6E3EC8ccE"
+---
+
+Полагаю, вы так же, как и мы, с нетерпением ждете возможности [развернуть](/developers/docs/smart-contracts/deploying/) и повзаимодействовать со своим первым [смарт-контрактом](/developers/docs/smart-contracts/) в блокчейне Ethereum.
+
+Не волнуйтесь, так как это наш первый смарт-контракт, мы развернем его в [локальной тестовой сети](/developers/docs/networks/), поэтому его развертывание и эксперименты с ним будут для вас совершенно бесплатными.
+
+## Написание нашего контракта {#writing-our-contract}
+
+Первым делом [посетите Remix](https://remix.ethereum.org/) и создайте новый файл. В левой верхней части интерфейса Remix добавьте новый файл и введите желаемое имя файла.
+
+
+
+В новый файл мы вставим следующий код.
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.5.17;
+
+contract Counter {
+
+ // Открытая переменная типа unsigned int для хранения количества подсчетов
+ uint256 public count = 0;
+
+ // Функция, которая увеличивает наш счетчик
+ function increment() public {
+ count += 1;
+ }
+
+ // Необязательный геттер для получения значения счетчика
+ function getCount() public view returns (uint256) {
+ return count;
+ }
+
+}
+```
+
+Если вы знакомы с программированием, вы легко догадаетесь, что делает эта программа. Вот построчное объяснение:
+
+- Строка 4: мы определяем контракт с именем `Counter`.
+- Строка 7: наш контракт хранит одно беззнаковое целое число с именем `count`, начальное значение которого равно 0.
+- Строка 10: первая функция изменит состояние контракта и увеличит (`increment()`) значение нашей переменной `count`.
+- Строка 15: вторая функция — это просто геттер, позволяющий считывать значение переменной `count` извне смарт-контракта. Обратите внимание, что, поскольку мы определили нашу переменную `count` как public, в этом нет необходимости, но это показано в качестве примера.
+
+Это все, что касается нашего первого простого смарт-контракта. Как вы, возможно, знаете, он похож на класс из языков ООП (объектно-ориентированного программирования), таких как Java или C++. Теперь пришло время поэкспериментировать с нашим контрактом.
+
+## Развертывание нашего контракта {#deploying-our-contract}
+
+Поскольку мы написали наш самый первый смарт-контракт, теперь мы развернем его в блокчейне, чтобы поэкспериментировать с ним.
+
+[Развертывание смарт-контракта в блокчейне](/developers/docs/smart-contracts/deploying/) — это, по сути, просто отправка транзакции, содержащей код скомпилированного смарт-контракта, без указания получателей.
+
+Сначала мы [скомпилируем контракт](/developers/docs/smart-contracts/compiling/), нажав на значок компиляции в левой части:
+
+
+
+Затем нажмите кнопку компиляции:
+
+
+
+Вы можете выбрать опцию «Auto compile», чтобы контракт всегда компилировался при сохранении содержимого в текстовом редакторе.
+
+Затем перейдите на экран «deploy and run transactions»:
+
+
+
+Когда вы окажетесь на экране «deploy and run transactions», дважды проверьте, что отображается имя вашего контракта, и нажмите «Deploy». Как видно в верхней части страницы, текущая среда — «JavaScript VM». Это означает, что мы будем развертывать наш смарт-контракт и взаимодействовать с ним в локальном тестовом блокчейне, чтобы иметь возможность проводить тестирование быстрее и без каких-либо комиссий.
+
+
+
+После нажатия кнопки «Deploy» вы увидите свой контракт в нижней части экрана. Нажмите на стрелку слева, чтобы развернуть его и увидеть содержимое нашего контракта. Это наша переменная `counter`, наша функция `increment()` и геттер `getCounter()`.
+
+Если вы нажмете на кнопку `count` или `getCount`, она получит и отобразит содержимое переменной `count` контракта. Поскольку мы еще не вызывали функцию `increment`, она должна отобразить 0.
+
+
+
+Теперь давайте вызовем функцию `increment`, нажав на соответствующую кнопку. В нижней части окна вы увидите журналы выполненных транзакций. Вы увидите, что журналы отличаются, когда вы нажимаете кнопку для получения данных, а не кнопку `increment`. Это потому, что чтение данных из блокчейна не требует каких-либо транзакций (записи) или комиссий. Потому что только изменение состояния блокчейна требует выполнения транзакции:
+
+
+
+После нажатия кнопки `increment`, которая создаст транзакцию для вызова нашей функции `increment()`, если мы снова нажмем на кнопки `count` или `getCount`, мы прочтем новое обновленное состояние нашего смарт-контракта, в котором переменная `count` будет больше 0.
+
+
+
+В следующем руководстве мы рассмотрим, [как добавлять события в смарт-контракты](/developers/tutorials/logging-events-smart-contracts/). Ведение журнала событий — это удобный способ отладить ваш смарт-контракт и понять, что происходит при вызове функции.
diff --git a/public/content/translations/ru/developers/tutorials/develop-and-test-dapps-with-a-multi-client-local-eth-testnet/index.md b/public/content/translations/ru/developers/tutorials/develop-and-test-dapps-with-a-multi-client-local-eth-testnet/index.md
new file mode 100644
index 00000000000..9955e911e61
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/develop-and-test-dapps-with-a-multi-client-local-eth-testnet/index.md
@@ -0,0 +1,372 @@
+---
+title: "Как разработать и протестировать децентрализованное приложение в локальной многоклиентской тестовой сети"
+description: "В этом руководстве вы сначала узнаете, как создать и настроить локальную многоклиентскую тестовую сеть Ethereum, а затем использовать эту тестовую сеть для развертывания и тестирования децентрализованного приложения."
+author: "Tedi Mitiku"
+tags:
+ [
+ "клиенты",
+ "узлы",
+ "Умные контракты",
+ "композиционность",
+ "уровень консенсуса",
+ "Уровень исполнения",
+ "тестирование"
+ ]
+skill: intermediate
+lang: ru
+published: 2023-04-11
+---
+
+## Введение {#introduction}
+
+Это руководство проведет вас через процесс создания настраиваемой локальной тестовой сети Ethereum, развертывания в ней смарт-контракта и использования тестовой сети для запуска тестов для вашего децентрализованного приложения. Это руководство предназначено для разработчиков децентрализованных приложений, которые хотят разрабатывать и тестировать свои децентрализованные приложения локально с различными конфигурациями сети перед развертыванием в работающую тестовую сеть или основную сеть.
+
+В этом руководстве вы:
+
+- Создадите локальную тестовую сеть Ethereum с помощью [`eth-network-package`](https://github.com/kurtosis-tech/eth-network-package), используя [Kurtosis](https://www.kurtosis.com/),
+- Подключите среду разработки децентрализованных приложений Hardhat к локальной тестовой сети, чтобы скомпилировать, развернуть и протестировать децентрализованное приложение, и
+- Настроите локальную тестовую сеть, включая такие параметры, как количество узлов и конкретные пары клиентов EL/CL, чтобы обеспечить рабочие процессы разработки и тестирования для различных конфигураций сети.
+
+### Что такое Kurtosis? {#what-is-kurtosis}
+
+[Kurtosis](https://www.kurtosis.com/) — это компонуемая система сборки, предназначенная для настройки многоконтейнерных тестовых сред. Он специально позволяет разработчикам создавать воспроизводимые среды, требующие динамической логики настройки, например, тестовые сети блокчейна.
+
+В этом руководстве пакет Kurtosis eth-network-package запускает локальную тестовую сеть Ethereum с поддержкой клиента уровня исполнения (EL) [`geth`](https://geth.ethereum.org/), а также клиентов уровня консенсуса (CL) [`teku`](https://consensys.io/teku), [`lighthouse`](https://lighthouse.sigmaprime.io/) и [`lodestar`](https://lodestar.chainsafe.io/). Этот пакет служит настраиваемой и компонуемой альтернативой сетям во фреймворках, таких как Hardhat Network, Ganache и Anvil. Kurtosis предлагает разработчикам больший контроль и гибкость над используемыми ими тестовыми сетями, что является основной причиной, по которой [Ethereum Foundation использовал Kurtosis для тестирования Слияния](https://www.kurtosis.com/blog/testing-the-ethereum-merge) и продолжает использовать его для тестирования обновлений сети.
+
+## Настройка Kurtosis {#setting-up-kurtosis}
+
+Прежде чем продолжить, убедитесь, что у вас есть:
+
+- [Установленный и запущенный движок Docker](https://docs.kurtosis.com/install/#i-install--start-docker) на вашем локальном компьютере
+- [Установленный Kurtosis CLI](https://docs.kurtosis.com/install#ii-install-the-cli) (или обновленный до последней версии, если CLI у вас уже установлен)
+- Установленные [Node.js](https://nodejs.org/en), [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable) и [npx](https://www.npmjs.com/package/npx) (для вашей среды децентрализованных приложений)
+
+## Создание локальной тестовой сети Ethereum {#instantiate-testnet}
+
+Чтобы запустить локальную тестовую сеть Ethereum, выполните:
+
+```python
+kurtosis --enclave local-eth-testnet run github.com/kurtosis-tech/eth-network-package
+```
+
+Примечание: эта команда дает вашей сети имя «local-eth-testnet» с помощью флага `--enclave`.
+
+Kurtosis выведет на экран шаги, которые он выполняет «под капотом» в процессе интерпретации, проверки и последующего выполнения инструкций. В конце вы должны увидеть вывод, похожий на следующий:
+
+```python
+INFO[2023-04-04T18:09:44-04:00] ======================================================
+INFO[2023-04-04T18:09:44-04:00] || Created enclave: local-eth-testnet ||
+INFO[2023-04-04T18:09:44-04:00] ======================================================
+Name: local-eth-testnet
+UUID: 39372d756ae8
+Status: RUNNING
+Creation Time: Tue, 04 Apr 2023 18:09:03 EDT
+
+========================================= Files Artifacts =========================================
+UUID Name
+d4085a064230 cl-genesis-data
+1c62cb792e4c el-genesis-data
+bd60489b73a7 genesis-generation-config-cl
+b2e593fe5228 genesis-generation-config-el
+d552a54acf78 geth-prefunded-keys
+5f7e661eb838 prysm-password
+054e7338bb59 validator-keystore-0
+
+========================================== User Services ==========================================
+UUID Name Ports Status
+e20f129ee0c5 cl-client-0-beacon http: 4000/tcp -> RUNNING
+ metrics: 5054/tcp ->
+ tcp-discovery: 9000/tcp -> 127.0.0.1:54263
+ udp-discovery: 9000/udp -> 127.0.0.1:60470
+a8b6c926cdb4 cl-client-0-validator http: 5042/tcp -> 127.0.0.1:54267 RUNNING
+ metrics: 5064/tcp ->
+d7b802f623e8 el-client-0 engine-rpc: 8551/tcp -> 127.0.0.1:54253 RUNNING
+ rpc: 8545/tcp -> 127.0.0.1:54251
+ tcp-discovery: 30303/tcp -> 127.0.0.1:54254
+ udp-discovery: 30303/udp -> 127.0.0.1:53834
+ ws: 8546/tcp -> 127.0.0.1:54252
+514a829c0a84 prelaunch-data-generator-1680646157905431468 STOPPED
+62bd62d0aa7a prelaunch-data-generator-1680646157915424301 STOPPED
+05e9619e0e90 prelaunch-data-generator-1680646157922872635 STOPPED
+
+```
+
+Поздравляем! Вы использовали Kurtosis для создания локальной тестовой сети Ethereum с клиентом CL (`lighthouse`) и клиентом EL (`geth`) через Docker.
+
+### Обзор {#review-instantiate-testnet}
+
+В этом разделе вы выполнили команду, которая указала Kurtosis использовать [`eth-network-package`, размещенный удаленно на GitHub](https://github.com/kurtosis-tech/eth-network-package), для запуска локальной тестовой сети Ethereum в Kurtosis [Enclave](https://docs.kurtosis.com/advanced-concepts/enclaves/). Внутри вашего анклава вы найдете как «файловые артефакты», так и «пользовательские службы».
+
+[Файловые артефакты](https://docs.kurtosis.com/advanced-concepts/files-artifacts/) в вашем анклаве включают все данные, сгенерированные и используемые для начальной загрузки клиентов EL и CL. Данные были созданы с помощью службы `prelaunch-data-generator`, созданной на основе этого [образа Docker](https://github.com/ethpandaops/ethereum-genesis-generator).
+
+Пользовательские службы отображают все контейнеризированные службы, работающие в вашем анклаве. Вы заметите, что был создан один узел, включающий как клиент EL, так и клиент CL.
+
+## Подключите среду разработки децентрализованного приложения к локальной тестовой сети Ethereum {#connect-your-dapp}
+
+### Настройка среды разработки децентрализованного приложения {#set-up-dapp-env}
+
+Теперь, когда у вас есть запущенная локальная тестовая сеть, вы можете подключить свою среду разработки децентрализованного приложения для использования локальной тестовой сети. Фреймворк Hardhat будет использоваться в этом руководстве для развертывания децентрализованного приложения для игры в блэкджек в вашей локальной тестовой сети.
+
+Чтобы настроить среду разработки децентрализованного приложения, клонируйте репозиторий, содержащий наше примерное децентрализованное приложение, и установите его зависимости, выполнив команду:
+
+```python
+git clone https://github.com/kurtosis-tech/awesome-kurtosis.git && cd awesome-kurtosis/smart-contract-example && yarn
+```
+
+Папка [smart-contract-example](https://github.com/kurtosis-tech/awesome-kurtosis/tree/main/smart-contract-example), используемая здесь, содержит типичную настройку для разработчика децентрализованного приложения, использующего фреймворк [Hardhat](https://hardhat.org/):
+
+- [`contracts/`](https://github.com/kurtosis-tech/awesome-kurtosis/tree/main/smart-contract-example/contracts) содержит несколько простых смарт-контрактов для децентрализованного приложения Blackjack
+- [`scripts/`](https://github.com/kurtosis-tech/awesome-kurtosis/tree/main/smart-contract-example/scripts) содержит скрипт для развертывания контракта токена в вашей локальной сети Ethereum
+- [`test/`](https://github.com/kurtosis-tech/awesome-kurtosis/tree/main/smart-contract-example/test) содержит простой тест на .js для вашего контракта токена, чтобы подтвердить, что для каждого игрока в нашем децентрализованном приложении Blackjack отчеканено 1000 фишек
+- [`hardhat.config.ts`](https://github.com/kurtosis-tech/awesome-kurtosis/blob/main/smart-contract-example/hardhat.config.ts) настраивает вашу установку Hardhat
+
+### Настройка Hardhat для использования локальной тестовой сети {#configure-hardhat}
+
+После настройки среды разработки децентрализованного приложения вы подключите Hardhat для использования локальной тестовой сети Ethereum, созданной с помощью Kurtosis. Для этого замените `<$YOUR_PORT>` в структуре `localnet` в файле конфигурации `hardhat.config.ts` на порт из вывода RPC URI любой службы `el-client-`. В этом примере порт будет `64248`. Ваш порт будет другим.
+
+Пример в `hardhat.config.ts`:
+
+```js
+localnet: {
+url: 'http://127.0.0.1:<$YOUR_PORT>',// TODO: ЗАМЕНИТЕ $YOUR_PORT НА ПОРТ URI УЗЛА, ВЫДАННЫЙ ПАКЕТОМ СЕТИ ETH KURTOSIS
+
+// Это приватные ключи, связанные с предварительно пополненными тестовыми аккаунтами, созданными пакетом eth-network-package
+//
+accounts: [
+ "ef5177cd0b6b21c87db5a0bf35d4084a8a57a9d6a064f86d51ac85f2b873a4e2",
+ "48fcc39ae27a0e8bf0274021ae6ebd8fe4a0e12623d61464c498900b28feb567",
+ "7988b3a148716ff800414935b305436493e1f25237a2a03e5eebc343735e2f31",
+ "b3c409b6b0b3aa5e65ab2dc1930534608239a478106acf6f3d9178e9f9b00b35",
+ "df9bb6de5d3dc59595bcaa676397d837ff49441d211878c024eabda2cd067c9f",
+ "7da08f856b5956d40a72968f93396f6acff17193f013e8053f6fbb6c08c194d6",
+ ],
+},
+```
+
+После сохранения файла ваша среда разработки децентрализованных приложений Hardhat будет подключена к вашей локальной тестовой сети Ethereum! Вы можете убедиться, что ваша тестовая сеть работает, выполнив команду:
+
+```python
+npx hardhat balances --network localnet
+```
+
+Вывод должен выглядеть примерно так:
+
+```python
+0x878705ba3f8Bc32FCf7F4CAa1A35E72AF65CF766 has balance 10000000000000000000000000
+0x4E9A3d9D1cd2A2b2371b8b3F489aE72259886f1A has balance 10000000000000000000000000
+0xdF8466f277964Bb7a0FFD819403302C34DCD530A has balance 10000000000000000000000000
+0x5c613e39Fc0Ad91AfDA24587e6f52192d75FBA50 has balance 10000000000000000000000000
+0x375ae6107f8cC4cF34842B71C6F746a362Ad8EAc has balance 10000000000000000000000000
+0x1F6298457C5d76270325B724Da5d1953923a6B88 has balance 10000000000000000000000000
+```
+
+Это подтверждает, что Hardhat использует вашу локальную тестовую сеть и обнаруживает предварительно пополненные аккаунты, созданные `eth-network-package`.
+
+### Развертывание и тестирование вашего децентрализованного приложения локально {#deploy-and-test-dapp}
+
+Когда среда разработки децентрализованного приложения полностью подключена к локальной тестовой сети Ethereum, вы можете запускать рабочие процессы разработки и тестирования для вашего децентрализованного приложения, используя локальную тестовую сеть.
+
+Чтобы скомпилировать и развернуть смарт-контракт `ChipToken.sol` для локального прототипирования и разработки, выполните:
+
+```python
+npx hardhat compile
+npx hardhat run scripts/deploy.ts --network localnet
+```
+
+Вывод должен выглядеть примерно так:
+
+```python
+ChipToken развернут по адресу: 0xAb2A01BC351770D09611Ac80f1DE076D56E0487d
+```
+
+Теперь попробуйте запустить тест `simple.js` для вашего локального децентрализованного приложения, чтобы подтвердить, что для каждого игрока в нашем децентрализованном приложении Blackjack отчеканено 1000 фишек:
+
+Вывод должен выглядеть примерно так:
+
+```python
+npx hardhat test --network localnet
+```
+
+Вывод должен выглядеть примерно так:
+
+```python
+ChipToken
+ чеканка
+ ✔ должно быть отчеканено 1000 фишек для ИГРОКА ОДИН
+
+ 1 пройден (654 мс)
+```
+
+### Обзор {#review-dapp-workflows}
+
+К этому моменту вы настроили среду разработки децентрализованного приложения, подключили ее к локальной сети Ethereum, созданной Kurtosis, и скомпилировали, развернули и запустили простой тест для вашего децентрализованного приложения.
+
+Теперь давайте рассмотрим, как можно настроить базовую сеть для тестирования наших децентрализованных приложений в различных конфигурациях сети.
+
+## Настройка локальной тестовой сети Ethereum {#configure-testnet}
+
+### Изменение конфигураций клиентов и количества узлов {#configure-client-config-and-num-nodes}
+
+Ваша локальная тестовая сеть Ethereum может быть настроена для использования различных пар клиентов EL и CL, а также различного количества узлов, в зависимости от сценария и конкретной конфигурации сети, которую вы хотите разработать или протестировать. Это означает, что после настройки вы можете запустить настраиваемую локальную тестовую сеть и использовать ее для выполнения тех же рабочих процессов (развертывание, тесты и т. д.) в различных конфигурациях сети, чтобы убедиться, что все работает так, как ожидалось. Чтобы узнать больше о других параметрах, которые вы можете изменить, перейдите по этой ссылке.
+
+Попробуйте! Вы можете передавать различные параметры конфигурации в `eth-network-package` через JSON-файл. Этот JSON-файл с параметрами сети предоставляет конкретные конфигурации, которые Kurtosis будет использовать для настройки локальной сети Ethereum.
+
+Возьмите файл конфигурации по умолчанию и отредактируйте его, чтобы запустить три узла с разными парами EL/CL:
+
+- Узел 1 с `geth`/`lighthouse`
+- Узел 2 с `geth`/`lodestar`
+- Узел 3 с `geth`/`teku`
+
+Эта конфигурация создает гетерогенную сеть реализаций узлов Ethereum для тестирования вашего децентрализованного приложения. Ваш файл конфигурации теперь должен выглядеть так:
+
+```yaml
+{
+ "participants":
+ [
+ {
+ "el_client_type": "geth",
+ "el_client_image": "",
+ "el_client_log_level": "",
+ "cl_client_type": "lighthouse",
+ "cl_client_image": "",
+ "cl_client_log_level": "",
+ "beacon_extra_params": [],
+ "el_extra_params": [],
+ "validator_extra_params": [],
+ "builder_network_params": null,
+ },
+ {
+ "el_client_type": "geth",
+ "el_client_image": "",
+ "el_client_log_level": "",
+ "cl_client_type": "lodestar",
+ "cl_client_image": "",
+ "cl_client_log_level": "",
+ "beacon_extra_params": [],
+ "el_extra_params": [],
+ "validator_extra_params": [],
+ "builder_network_params": null,
+ },
+ {
+ "el_client_type": "geth",
+ "el_client_image": "",
+ "el_client_log_level": "",
+ "cl_client_type": "teku",
+ "cl_client_image": "",
+ "cl_client_log_level": "",
+ "beacon_extra_params": [],
+ "el_extra_params": [],
+ "validator_extra_params": [],
+ "builder_network_params": null,
+ },
+ ],
+ "network_params":
+ {
+ "preregistered_validator_keys_mnemonic": "giant issue aisle success illegal bike spike question tent bar rely arctic volcano long crawl hungry vocal artwork sniff fantasy very lucky have athlete",
+ "num_validator_keys_per_node": 64,
+ "network_id": "3151908",
+ "deposit_contract_address": "0x4242424242424242424242424242424242424242",
+ "seconds_per_slot": 12,
+ "genesis_delay": 120,
+ "capella_fork_epoch": 5,
+ },
+}
+```
+
+Каждая структура `participants` сопоставляется с узлом в сети, поэтому 3 структуры `participants` сообщат Kurtosis о запуске 3 узлов в вашей сети. Каждая структура `participants` позволит вам указать пару EL и CL, используемую для этого конкретного узла.
+
+Структура `network_params` настраивает параметры сети, которые используются для создания файлов генезиса для каждого узла, а также другие настройки, такие как количество секунд на слот в сети.
+
+Сохраните отредактированный файл параметров в любом каталоге (в примере ниже он сохранен на рабочем столе), а затем используйте его для запуска пакета Kurtosis, выполнив:
+
+```python
+kurtosis clean -a && kurtosis run --enclave local-eth-testnet github.com/kurtosis-tech/eth-network-package "$(cat ~/eth-network-params.json)"
+```
+
+Примечание: команда `kurtosis clean -a` используется здесь, чтобы указать Kurtosis уничтожить старую тестовую сеть и ее содержимое перед запуском новой.
+
+Опять же, Kurtosis будет некоторое время работать и выводить на печать отдельные шаги, которые выполняются. В конечном итоге вывод должен выглядеть примерно так:
+
+```python
+Starlark code successfully run. No output was returned.
+INFO[2023-04-07T11:43:16-04:00] ==========================================================
+INFO[2023-04-07T11:43:16-04:00] || Created enclave: local-eth-testnet ||
+INFO[2023-04-07T11:43:16-04:00] ==========================================================
+Name: local-eth-testnet
+UUID: bef8c192008e
+Status: RUNNING
+Creation Time: Fri, 07 Apr 2023 11:41:58 EDT
+
+========================================= Files Artifacts =========================================
+UUID Name
+cc495a8e364a cl-genesis-data
+7033fcdb5471 el-genesis-data
+a3aef43fc738 genesis-generation-config-cl
+8e968005fc9d genesis-generation-config-el
+3182cca9d3cd geth-prefunded-keys
+8421166e234f prysm-password
+d9e6e8d44d99 validator-keystore-0
+23f5ba517394 validator-keystore-1
+4d28dea40b5c validator-keystore-2
+
+========================================== User Services ==========================================
+UUID Name Ports Status
+485e6fde55ae cl-client-0-beacon http: 4000/tcp -> http://127.0.0.1:65010 RUNNING
+ metrics: 5054/tcp -> http://127.0.0.1:65011
+ tcp-discovery: 9000/tcp -> 127.0.0.1:65012
+ udp-discovery: 9000/udp -> 127.0.0.1:54455
+73739bd158b2 cl-client-0-validator http: 5042/tcp -> 127.0.0.1:65016 RUNNING
+ metrics: 5064/tcp -> http://127.0.0.1:65017
+1b0a233cd011 cl-client-1-beacon http: 4000/tcp -> 127.0.0.1:65021 RUNNING
+ metrics: 8008/tcp -> 127.0.0.1:65023
+ tcp-discovery: 9000/tcp -> 127.0.0.1:65024
+ udp-discovery: 9000/udp -> 127.0.0.1:56031
+ validator-metrics: 5064/tcp -> 127.0.0.1:65022
+949b8220cd53 cl-client-1-validator http: 4000/tcp -> 127.0.0.1:65028 RUNNING
+ metrics: 8008/tcp -> 127.0.0.1:65030
+ tcp-discovery: 9000/tcp -> 127.0.0.1:65031
+ udp-discovery: 9000/udp -> 127.0.0.1:60784
+ validator-metrics: 5064/tcp -> 127.0.0.1:65029
+c34417bea5fa cl-client-2 http: 4000/tcp -> 127.0.0.1:65037 RUNNING
+ metrics: 8008/tcp -> 127.0.0.1:65035
+ tcp-discovery: 9000/tcp -> 127.0.0.1:65036
+ udp-discovery: 9000/udp -> 127.0.0.1:63581
+e19738e6329d el-client-0 engine-rpc: 8551/tcp -> 127.0.0.1:64986 RUNNING
+ rpc: 8545/tcp -> 127.0.0.1:64988
+ tcp-discovery: 30303/tcp -> 127.0.0.1:64987
+ udp-discovery: 30303/udp -> 127.0.0.1:55706
+ ws: 8546/tcp -> 127.0.0.1:64989
+e904687449d9 el-client-1 engine-rpc: 8551/tcp -> 127.0.0.1:64993 RUNNING
+ rpc: 8545/tcp -> 127.0.0.1:64995
+ tcp-discovery: 30303/tcp -> 127.0.0.1:64994
+ udp-discovery: 30303/udp -> 127.0.0.1:58096
+ ws: 8546/tcp -> 127.0.0.1:64996
+ad6f401126fa el-client-2 engine-rpc: 8551/tcp -> 127.0.0.1:65003 RUNNING
+ rpc: 8545/tcp -> 127.0.0.1:65001
+ tcp-discovery: 30303/tcp -> 127.0.0.1:65000
+ udp-discovery: 30303/udp -> 127.0.0.1:57269
+ ws: 8546/tcp -> 127.0.0.1:65002
+12d04a9dbb69 prelaunch-data-generator-1680882122181135513 STOPPED
+5b45f9c0504b prelaunch-data-generator-1680882122192182847 STOPPED
+3d4aaa75e218 prelaunch-data-generator-1680882122201668972 STOPPED
+```
+
+Поздравляем! Вы успешно настроили свою локальную тестовую сеть, чтобы в ней было 3 узла вместо 1. Чтобы запустить те же рабочие процессы, что и раньше, для вашего децентрализованного приложения (развертывание и тестирование), выполните те же операции, что и раньше, заменив `<$YOUR_PORT>` в структуре `localnet` в вашем файле конфигурации `hardhat.config.ts` на порт из вывода RPC URI любой службы `el-client-` в вашей новой 3-узловой локальной тестовой сети.
+
+## Заключение {#conclusion}
+
+Вот и все! Подводя итоги этого краткого руководства, вы:
+
+- Создали локальную тестовую сеть Ethereum через Docker с помощью Kurtosis
+- Подключили свою локальную среду разработки децентрализованных приложений к локальной сети Ethereum
+- Развернули децентрализованное приложение и запустили для него простой тест в локальной сети Ethereum
+- Настроили базовую сеть Ethereum на 3 узла
+
+Мы будем рады услышать от вас о том, что у вас получилось, что можно улучшить, или ответить на любые ваши вопросы. Не стесняйтесь обращаться к нам через [GitHub](https://github.com/kurtosis-tech/kurtosis/issues/new/choose) или [пишите нам по электронной почте](mailto:feedback@kurtosistech.com)!
+
+### Другие примеры и руководства {#other-examples-guides}
+
+Мы рекомендуем вам ознакомиться с нашим [кратким руководством](https://docs.kurtosis.com/quickstart) и другими примерами в нашем [репозитории awesome-kurtosis](https://github.com/kurtosis-tech/awesome-kurtosis), где вы найдете несколько отличных примеров, включая пакеты для:
+
+- Запуск той же локальной тестовой сети Ethereum, но с подключением дополнительных служб, таких как спамер транзакций (для имитации транзакций), монитор форков и подключенные экземпляры Grafana и Prometheus
+- Проведение [теста подсети](https://github.com/kurtosis-tech/awesome-kurtosis/tree/main/ethereum-network-partition-test) в той же локальной сети Ethereum
diff --git a/public/content/translations/ru/developers/tutorials/downsizing-contracts-to-fight-the-contract-size-limit/index.md b/public/content/translations/ru/developers/tutorials/downsizing-contracts-to-fight-the-contract-size-limit/index.md
new file mode 100644
index 00000000000..30476a3a7b6
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/downsizing-contracts-to-fight-the-contract-size-limit/index.md
@@ -0,0 +1,144 @@
+---
+title: "Уменьшение размера контрактов для борьбы с ограничением их размера"
+description: "Что можно сделать, чтобы ваши смарт-контракты не становились слишком большими?"
+author: Markus Waas
+lang: ru
+tags: [ "твердость", "Умные контракты", "хранилище" ]
+skill: intermediate
+published: 2020-06-26
+source: soliditydeveloper.com
+sourceUrl: https://soliditydeveloper.com/max-contract-size
+---
+
+## Почему существует ограничение? {#why-is-there-a-limit}
+
+22 ноября 2016 г. в хардфорке [Spurious Dragon](https://blog.ethereum.org/2016/11/18/hard-fork-no-4-spurious-dragon/) было введено [EIP-170](https://eips.ethereum.org/EIPS/eip-170), которое добавило ограничение на размер смарт-контракта в 24,576 кб. Для вас как для разработчика на Solidity это означает, что по мере добавления все большей функциональности в ваш контракт в какой-то момент вы достигнете предела и при развертывании увидите ошибку:
+
+`Предупреждение: Размер кода контракта превышает 24576 байт (ограничение, введенное в Spurious Dragon).` Этот контракт может оказаться невозможно развернуть в основной сети. `Рассмотрите возможность включения оптимизатора (с низким значением \"runs\"!), отключения строк отката или использования библиотек.`
+
+Это ограничение было введено для предотвращения атак типа «отказ в обслуживании» (DoS-атак). Любой вызов контракта относительно дешев с точки зрения затрат газа. Однако влияние вызова контракта на узлы Ethereum непропорционально возрастает в зависимости от размера кода вызываемого контракта (чтение кода с диска, предварительная обработка кода, добавление данных в доказательство Меркла). Всякий раз, когда возникает ситуация, в которой атакующему требуется мало ресурсов, чтобы заставить других проделать большую работу, появляется возможность для DoS-атак.
+
+Изначально это было не такой большой проблемой, потому что одним из естественных ограничений размера контракта является лимит газа блока. Очевидно, что контракт должен быть развернут в рамках транзакции, которая содержит весь байткод контракта. Если вы включите в блок только эту одну транзакцию, вы можете использовать весь газ, но он не бесконечен. После [обновления London](/ethereum-forks/#london) лимит газа блока может варьироваться от 15 до 30 млн единиц в зависимости от загруженности сети.
+
+Далее мы рассмотрим некоторые методы, упорядоченные по их потенциальному влиянию. Думайте об этом как о похудении. Лучшая стратегия для достижения целевого веса (в нашем случае 24 кб) — это сначала сосредоточиться на методах с наибольшим влиянием. В большинстве случаев для достижения цели достаточно просто изменить свой рацион, но иногда требуется нечто большее. Затем вы можете добавить некоторые упражнения (среднее влияние) или даже пищевые добавки (небольшое влияние).
+
+## Значительное влияние {#big-impact}
+
+### Разделяйте свои контракты {#separate-your-contracts}
+
+Это всегда должно быть вашим первым шагом. Как можно разделить контракт на несколько меньших? Как правило, это заставляет вас разработать хорошую архитектуру для ваших контрактов. С точки зрения читаемости кода всегда предпочтительнее использовать контракты меньшего размера. Чтобы разделить контракты, задайте себе следующие вопросы:
+
+- Какие функции логически связаны? Каждый набор функций лучше всего разместить в отдельном контракте.
+- Какие функции не требуют чтения состояния контракта или требуют лишь определенного подмножества состояния?
+- Можно ли разделить хранилище и функциональность?
+
+### Библиотеки {#libraries}
+
+Один из простых способов отделить код функциональности от хранилища — использовать [библиотеку](https://solidity.readthedocs.io/en/v0.6.10/contracts.html#libraries). Не объявляйте функции библиотеки как internal, так как они будут [добавлены в контракт](https://ethereum.stackexchange.com/questions/12975/are-internal-functions-in-libraries-not-covered-by-linking) напрямую во время компиляции. Но если вы используете функции public, то они фактически будут находиться в отдельном контракте библиотеки. Рассмотрите возможность использования [using for](https://solidity.readthedocs.io/en/v0.6.10/contracts.html#using-for), чтобы сделать использование библиотек более удобным.
+
+### Прокси-контракты {#proxies}
+
+Более продвинутой стратегией является система прокси-контрактов. Библиотеки используют `DELEGATECALL` «под капотом», что просто выполняет функцию другого контракта с состоянием вызывающего контракта. Прочтите [эту статью в блоге](https://hackernoon.com/how-to-make-smart-contracts-upgradable-2612e771d5a2), чтобы узнать больше о системах прокси-контрактов. Они предоставляют вам больше функциональности, например, они обеспечивают возможность обновления, но они также добавляют много сложности. Я бы не стал добавлять их только для уменьшения размера контракта, если по какой-либо причине это не является вашим единственным вариантом.
+
+## Среднее влияние {#medium-impact}
+
+### Удаление функций {#remove-functions}
+
+Это должно быть очевидно. Функции довольно сильно увеличивают размер контракта.
+
+- **External**: Часто мы добавляем много функций `view` для удобства. Это совершенно нормально, пока вы не достигнете предела размера. Тогда вам, возможно, стоит подумать об удалении всех функций, кроме абсолютно необходимых.
+- **Internal**: Вы также можете удалить функции `internal`/`private` и просто встроить код, если функция вызывается только один раз.
+
+### Избегайте дополнительных переменных {#avoid-additional-variables}
+
+```solidity
+function get(uint id) returns (address,address) {
+ MyStruct memory myStruct = myStructs[id];
+ return (myStruct.addr1, myStruct.addr2);
+}
+```
+
+```solidity
+function get(uint id) returns (address,address) {
+ return (myStructs[id].addr1, myStructs[id].addr2);
+}
+```
+
+Такое простое изменение дает разницу в **0,28 кб**. Скорее всего, вы сможете найти много подобных ситуаций в ваших контрактах, и в сумме они могут дать значительную экономию.
+
+### Сократите сообщения об ошибках {#shorten-error-message}
+
+Длинные сообщения отката и, в частности, множество различных сообщений отката могут раздувать контракт. Вместо этого используйте короткие коды ошибок и декодируйте их в своем клиенте. Длинное сообщение может стать намного короче:
+
+```solidity
+require(msg.sender == owner, \"Только владелец этого контракта может вызывать эту функцию\");
+```
+
+```solidity
+require(msg.sender == owner, \"OW1\");
+```
+
+### Используйте пользовательские ошибки вместо сообщений об ошибках
+
+Пользовательские ошибки были введены в [Solidity 0.8.4](https://blog.soliditylang.org/2021/04/21/custom-errors/). Это отличный способ уменьшить размер ваших контрактов, потому что они кодируются в ABI как селекторы (так же, как и функции).
+
+```solidity
+error Unauthorized();
+
+if (msg.sender != owner) {
+ revert Unauthorized();
+}
+```
+
+### Рассмотрите возможность использования низкого значения runs в оптимизаторе {#consider-a-low-run-value-in-the-optimizer}
+
+Вы также можете изменить настройки оптимизатора. Значение по умолчанию, равное 200, означает, что он пытается оптимизировать байткод так, как если бы функция вызывалась 200 раз. Если вы измените его на 1, вы, по сути, даете команду оптимизатору оптимизировать код для случая, когда каждая функция выполняется только один раз. Оптимизированная для однократного выполнения функция означает, что она оптимизирована для самого развертывания. Имейте в виду, что **это увеличивает [стоимость газа](/developers/docs/gas/) за выполнение функций**, поэтому, возможно, вы не захотите этого делать.
+
+## Небольшое влияние {#small-impact}
+
+### Избегайте передачи структур в функции {#avoid-passing-structs-to-functions}
+
+Если вы используете [ABIEncoderV2](https://solidity.readthedocs.io/en/v0.6.10/layout-of-source-files.html#abiencoderv2), может помочь отказ от передачи структур в функции. Вместо того чтобы передавать параметр в виде структуры, передавайте необходимые параметры напрямую. В этом примере мы сэкономили еще **0,1 кб**.
+
+```solidity
+function get(uint id) returns (address,address) {
+ return _get(myStruct);
+}
+
+function _get(MyStruct memory myStruct) private view returns(address,address) {
+ return (myStruct.addr1, myStruct.addr2);
+}
+```
+
+```solidity
+function get(uint id) returns(address,address) {
+ return _get(myStructs[id].addr1, myStructs[id].addr2);
+}
+
+function _get(address addr1, address addr2) private view returns(address,address) {
+ return (addr1, addr2);
+}
+```
+
+### Объявляйте правильную область видимости для функций и переменных {#declare-correct-visibility-for-functions-and-variables}
+
+- Функции или переменные, которые вызываются только извне? Объявляйте их как `external`, а не `public`.
+- Функции или переменные, вызываемые только из самого контракта? Объявляйте их как `private` или `internal` вместо `public`.
+
+### Удаление модификаторов {#remove-modifiers}
+
+Модификаторы, особенно при интенсивном использовании, могут оказывать значительное влияние на размер контракта. Рассмотрите возможность их удаления и использования вместо них функций.
+
+```solidity
+modifier checkStuff() {}
+
+function doSomething() checkStuff {}
+```
+
+```solidity
+function checkStuff() private {}
+
+function doSomething() { checkStuff(); }
+```
+
+Эти советы должны помочь вам значительно уменьшить размер контракта. Еще раз, не могу не подчеркнуть: всегда стремитесь к разделению контрактов, если это возможно, для достижения наибольшего эффекта.
diff --git a/public/content/translations/ru/developers/tutorials/eip-1271-smart-contract-signatures/index.md b/public/content/translations/ru/developers/tutorials/eip-1271-smart-contract-signatures/index.md
new file mode 100644
index 00000000000..89e880e30d8
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/eip-1271-smart-contract-signatures/index.md
@@ -0,0 +1,129 @@
+---
+title: "EIP-1271: Подписание и проверка подписей умных контрактов"
+description: "Обзор создания и проверки подписей умных контрактов с использованием EIP-1271. Мы также рассмотрим реализацию EIP-1271, используемую в Safe (ранее Gnosis Safe), чтобы предоставить конкретный пример для разработчиков умных контрактов, на который они могут опереться."
+author: Nathan H. Leung
+lang: ru
+tags:
+ [
+ "eip-1271",
+ "смарт-контракты",
+ "проверка",
+ "подписание"
+ ]
+skill: intermediate
+published: 2023-01-12
+---
+
+Стандарт [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) позволяет умным контрактам проверять подписи.
+
+В этом руководстве мы даем обзор цифровых подписей, предыстории EIP-1271 и конкретной реализации EIP-1271, используемой [Safe](https://safe.global/) (ранее Gnosis Safe). В совокупности это может послужить отправной точкой для реализации EIP-1271 в ваших собственных контрактах.
+
+## Что такое подпись?
+
+В данном контексте подпись (точнее, «цифровая подпись») — это сообщение плюс некое доказательство того, что сообщение пришло от определенного человека/отправителя/адреса.
+
+Например, цифровая подпись может выглядеть так:
+
+1. Сообщение: «Я хочу войти на этот сайт с помощью своего кошелька Ethereum».
+2. Подписавший: Мой адрес `0x000…`
+3. Доказательство: Вот доказательство того, что я, `0x000…`, действительно создал все это сообщение (обычно это нечто криптографическое).
+
+Важно отметить, что цифровая подпись включает в себя и «сообщение», и «подпись».
+
+Почему? Например, если бы вы дали мне на подпись контракт, а я бы отрезал страницу с подписью и вернул вам только свои подписи без остальной части контракта, контракт не был бы действительным.
+
+Точно так же цифровая подпись ничего не значит без связанного с ней сообщения!
+
+## Почему существует EIP-1271?
+
+Чтобы создать цифровую подпись для использования в блокчейнах на базе Ethereum, вам обычно нужен секретный приватный ключ, который никто больше не знает. Это то, что делает вашу подпись вашей (никто другой не может создать такую же подпись, не зная секретного ключа).
+
+Ваш аккаунт Ethereum (т. е. ваш аккаунт во внешнем владении / EOA) имеет связанный с ним приватный ключ, и это тот приватный ключ, который обычно используется, когда веб-сайт или децентрализованное приложение запрашивает у вас подпись (например, для «Входа с помощью Ethereum»).
+
+Приложение может [проверить подпись](https://www.alchemy.com/docs/how-to-verify-a-message-signature-on-ethereum), которую вы создали, с помощью сторонней библиотеки, такой как ethers.js, [не зная вашего приватного ключа](https://en.wikipedia.org/wiki/Public-key_cryptography) и быть уверенным, что именно _вы_ создали подпись.
+
+> На самом деле, поскольку цифровые подписи EOA используют криптографию с открытым ключом, их можно генерировать и проверять **офчейн**! Именно так работает безгазовое голосование в DAO — вместо того, чтобы отправлять голоса ончейн, цифровые подписи могут создаваться и проверяться офчейн с помощью криптографических библиотек.
+
+В то время как аккаунты EOA имеют приватный ключ, аккаунты умных контрактов не имеют никакого приватного или секретного ключа (поэтому «Вход с помощью Ethereum» и т. п. не может нативно работать с аккаунтами умных контрактов).
+
+Проблема, которую призван решить EIP-1271: как мы можем определить, что подпись умного контракта действительна, если у умного контракта нет «секрета», который он может включить в подпись?
+
+## Как работает EIP-1271?
+
+Умные контракты не имеют приватных ключей, которые можно использовать для подписи сообщений. Так как же мы можем определить, является ли подпись подлинной?
+
+Что ж, одна из идей заключается в том, что мы можем просто _спросить_ у умного контракта, является ли подпись подлинной!
+
+EIP-1271 стандартизирует идею «запроса» у умного контракта о действительности данной подписи.
+
+Контракт, реализующий EIP-1271, должен иметь функцию `isValidSignature`, которая принимает сообщение и подпись. Затем контракт может запустить некоторую логику проверки (спецификация не предписывает здесь ничего конкретного) и вернуть значение, указывающее, действительна ли подпись.
+
+Если `isValidSignature` возвращает действительный результат, это практически означает, что контракт говорит: «да, я одобряю эту подпись + сообщение!»
+
+### Интерфейс
+
+Вот точный интерфейс в спецификации EIP-1271 (мы поговорим о параметре `_hash` ниже, но пока думайте о нем как о проверяемом сообщении):
+
+```jsx
+pragma solidity ^0.5.0;
+
+contract ERC1271 {
+
+ // bytes4(keccak256("isValidSignature(bytes32,bytes)")
+ bytes4 constant internal MAGICVALUE = 0x1626ba7e;
+
+ /**
+ * @dev Должна возвращать, является ли предоставленная подпись действительной для предоставленного хэша
+ * @param _hash Хэш данных для подписи
+ * @param _signature Массив байтов подписи, связанный с _hash
+ *
+ * ДОЛЖНА возвращать магическое значение bytes4 0x1626ba7e при успешном выполнении функции.
+ * НЕ ДОЛЖНА изменять состояние (с использованием STATICCALL для solc < 0.5, модификатора view для solc > 0.5)
+ * ДОЛЖНА разрешать внешние вызовы
+ */
+ function isValidSignature(
+ bytes32 _hash,
+ bytes memory _signature)
+ public
+ view
+ returns (bytes4 magicValue);
+}
+```
+
+## Пример реализации EIP-1271: Safe
+
+Контракты могут реализовывать `isValidSignature` многими способами — спецификация не говорит много о точной реализации.
+
+Одним из примечательных контрактов, реализующих EIP-1271, является Safe (ранее Gnosis Safe).
+
+В коде Safe функция `isValidSignature` [реализована](https://github.com/safe-global/safe-contracts/blob/main/contracts/handler/CompatibilityFallbackHandler.sol) так, что подписи можно создавать и проверять [двумя способами](https://ethereum.stackexchange.com/questions/122635/signing-messages-as-a-gnosis-safe-eip1271-support):
+
+1. Ончейн-сообщения
+ 1. Создание: владелец Safe создает новую транзакцию Safe, чтобы «подписать» сообщение, передавая сообщение в качестве данных в транзакцию. Как только достаточное количество владельцев подпишет транзакцию для достижения порога мультиподписи, транзакция транслируется и выполняется. В транзакции есть функция Safe (`signMessage(bytes calldata _data)`), которая добавляет сообщение в список «одобренных» сообщений.
+ 2. Проверка: вызовите `isValidSignature` в контракте Safe и передайте сообщение для проверки в качестве параметра сообщения, а [пустое значение для параметра подписи](https://github.com/safe-global/safe-contracts/blob/main/contracts/handler/CompatibilityFallbackHandler.sol#L32) (т. е. `0x`). Safe увидит, что параметр подписи пуст, и вместо криптографической проверки подписи он просто проверит, находится ли сообщение в списке «одобренных» сообщений.
+2. Офчейн-сообщения:
+ 1. Создание: владелец Safe создает сообщение офчейн, затем получает подписи от других владельцев Safe по отдельности, пока не будет достаточно подписей для преодоления порога одобрения мультиподписи.
+ 2. Проверка: вызовите `isValidSignature`. В параметр сообщения передайте сообщение, которое нужно проверить. В параметр подписи передайте все индивидуальные подписи владельцев Safe, объединенные вместе, одна за другой. Safe проверит, что подписей достаточно для достижения порога **и** что каждая подпись действительна. Если это так, он вернет значение, указывающее на успешную проверку подписи.
+
+## Что именно представляет собой параметр `_hash`? Почему бы не передать все сообщение целиком?
+
+Вы могли заметить, что функция `isValidSignature` в [интерфейсе EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) принимает не само сообщение, а параметр `_hash`. Это означает, что вместо передачи полного сообщения произвольной длины в `isValidSignature` мы передаем 32-байтовый хэш сообщения (обычно keccak256).
+
+Каждый байт calldata — т. е. данных параметров функции, передаваемых в функцию умного контракта — [стоит 16 единиц газа (4 единицы газа, если это нулевой байт)](https://eips.ethereum.org/EIPS/eip-2028), так что это может сэкономить много газа, если сообщение длинное.
+
+### Предыдущие спецификации EIP-1271
+
+Существуют спецификации EIP-1271, в которых функция `isValidSignature` имеет первый параметр типа `bytes` (произвольной длины, вместо фиксированной длины `bytes32`) и имя параметра `message`. Это [более старая версия](https://github.com/safe-global/safe-contracts/issues/391#issuecomment-1075427206) стандарта EIP-1271.
+
+## Как следует реализовывать EIP-1271 в моих собственных контрактах?
+
+Здесь спецификация очень открыта. Реализация Safe содержит несколько хороших идей:
+
+- Вы можете считать подписи EOA от «владельца» контракта действительными.
+- Вы можете хранить список одобренных сообщений и считать действительными только их.
+
+В конце концов, это зависит от вас как от разработчика контракта!
+
+## Заключение
+
+[EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) — это универсальный стандарт, который позволяет умным контрактам проверять подписи. Он открывает возможность для умных контрактов действовать больше как EOA — например, предоставляя способ для «Входа с помощью Ethereum» работать с умными контрактами — и может быть реализован многими способами (Safe имеет нетривиальную, интересную реализацию для рассмотрения).
diff --git a/public/content/translations/ru/developers/tutorials/erc-721-vyper-annotated-code/index.md b/public/content/translations/ru/developers/tutorials/erc-721-vyper-annotated-code/index.md
new file mode 100644
index 00000000000..2c0b67453b6
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/erc-721-vyper-annotated-code/index.md
@@ -0,0 +1,713 @@
+---
+title: "Пошаговый разбор контракта Vyper ERC-721"
+description: "Контракт ERC-721 от Ryuya Nakamura и принцип его работы"
+author: Ori Pomerantz
+lang: ru
+tags: [ "vyper", "erc-721", "python" ]
+skill: beginner
+published: 2021-04-01
+---
+
+## Введение {#introduction}
+
+Стандарт [ERC-721](/developers/docs/standards/tokens/erc-721/) используется для владения невзаимозаменяемыми токенами (NFT).
+Токены [ERC-20](/developers/docs/standards/tokens/erc-20/) ведут себя как товар, так как между отдельными токенами нет разницы.
+В отличие от них, токены ERC-721 предназначены для активов, которые похожи, но не идентичны, например, различные мультяшные
+коты или права собственности на различные объекты недвижимости.
+
+В этой статье мы проанализируем [контракт ERC-721 от Ryuya Nakamura](https://github.com/vyperlang/vyper/blob/master/examples/tokens/ERC721.vy).
+Этот контракт написан на [Vyper](https://vyper.readthedocs.io/en/latest/index.html), языке контрактов, похожем на Python, который разработан таким образом, чтобы
+затруднить написание небезопасного кода по сравнению с Solidity.
+
+## Контракт {#contract}
+
+```python
+# @dev Реализация стандарта невзаимозаменяемых токенов ERC-721.
+# @author Ryuya Nakamura (@nrryuya)
+# Изменено из: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
+```
+
+Комментарии в Vyper, как и в Python, начинаются с символа решетки (`#`) и продолжаются до конца строки. Комментарии, которые включают
+`@`, используются [NatSpec](https://vyper.readthedocs.io/en/latest/natspec.html) для создания удобочитаемой
+документации.
+
+```python
+from vyper.interfaces import ERC721
+
+implements: ERC721
+```
+
+Интерфейс ERC-721 встроен в язык Vyper.
+[Определение кода можно посмотреть здесь](https://github.com/vyperlang/vyper/blob/master/vyper/builtin_interfaces/ERC721.py).
+Определение интерфейса написано на Python, а не на Vyper, потому что интерфейсы используются не только в
+блокчейне, но и при отправке транзакции в блокчейн с внешнего клиента, который может быть написан на
+Python.
+
+Первая строка импортирует интерфейс, а вторая указывает, что мы реализуем его здесь.
+
+### Интерфейс ERC721Receiver {#receiver-interface}
+
+```python
+# Интерфейс для контракта, вызываемого safeTransferFrom()
+interface ERC721Receiver:
+ def onERC721Received(
+```
+
+ERC-721 поддерживает два типа перевода:
+
+- `transferFrom`, который позволяет отправителю указать любой адрес назначения и возлагает ответственность
+ за перевод на отправителя. Это означает, что вы можете перевести средства на недействительный адрес, и в этом случае
+ NFT будет безвозвратно утерян.
+- `safeTransferFrom`, который проверяет, является ли адрес назначения контрактом. Если это так, контракт ERC-721
+ спрашивает у принимающего контракта, хочет ли он получить NFT.
+
+Чтобы отвечать на запросы `safeTransferFrom`, принимающий контракт должен реализовывать `ERC721Receiver`.
+
+```python
+ _operator: address,
+ _from: address,
+```
+
+Адрес `_from` — это текущий владелец токена. Адрес `_operator` — это адрес, который
+запросил перевод (эти два адреса могут не совпадать из-за разрешений).
+
+```python
+ _tokenId: uint256,
+```
+
+Идентификаторы токенов ERC-721 имеют размер 256 бит. Обычно они создаются путем хэширования описания того, что
+представляет собой токен.
+
+```python
+ _data: Bytes[1024]
+```
+
+Запрос может содержать до 1024 байт пользовательских данных.
+
+```python
+ ) -> bytes32: view
+```
+
+Чтобы предотвратить случаи, когда контракт случайно принимает перевод, возвращаемое значение является не логическим,
+а 256-битным значением с определенным содержанием.
+
+Эта функция является `view`, что означает, что она может читать состояние блокчейна, но не изменять его.
+
+### События {#events}
+
+[События](https://media.consensys.net/technical-introduction-to-events-and-logs-in-ethereum-a074d65dd61e)
+создаются для информирования пользователей и серверов за пределами блокчейна о событиях. Обратите внимание, что содержимое событий
+недоступно контрактам в блокчейне.
+
+```python
+# @dev Создается, когда право собственности на любой NFT изменяется любым механизмом. Это событие создается при создании NFT (`from` == 0) и их уничтожении (`to` == 0). Исключение: во время создания контракта любое
+# количество NFT может быть создано и назначено без создания события Transfer. Во время любого
+# перевода одобренный адрес для этого NFT (если он есть) сбрасывается.
+# @param _from Отправитель NFT (если адрес нулевой, это указывает на создание токена).
+# @param _to Получатель NFT (если адрес нулевой, это указывает на уничтожение токена).
+# @param _tokenId Переданный NFT.
+event Transfer:
+ sender: indexed(address)
+ receiver: indexed(address)
+ tokenId: indexed(uint256)
+```
+
+Это похоже на событие Transfer в ERC-20, за исключением того, что мы сообщаем `tokenId` вместо суммы.
+Никто не владеет нулевым адресом, поэтому по соглашению мы используем его для сообщения о создании и уничтожении токенов.
+
+```python
+# @dev Создается, когда одобренный адрес для NFT изменяется или подтверждается. Нулевой
+# адрес указывает на отсутствие одобренного адреса. Когда создается событие Transfer, это также
+# указывает, что одобренный адрес для этого NFT (если таковой имеется) сбрасывается.
+# @param _owner Владелец NFT.
+# @param _approved Адрес, который мы одобряем.
+# @param _tokenId NFT, который мы одобряем.
+event Approval:
+ owner: indexed(address)
+ approved: indexed(address)
+ tokenId: indexed(uint256)
+```
+
+Одобрение в ERC-721 похоже на разрешение в ERC-20. Определенному адресу разрешается переводить определенный
+токен. Это дает контрактам механизм для реагирования, когда они принимают токен. Контракты не могут
+прослушивать события, поэтому, если вы просто переведете им токен, они не \"узнают\" об этом. Таким образом, владелец
+сначала отправляет одобрение, а затем отправляет запрос контракту: \"Я одобрил для вас перевод токена
+X, пожалуйста, сделайте ...\".
+
+Это проектное решение, призванное сделать стандарт ERC-721 похожим на стандарт ERC-20. Поскольку
+токены ERC-721 не являются взаимозаменяемыми, контракт также может определить, что он получил определенный токен,
+проверив право собственности на токен.
+
+```python
+# @dev Это событие создается, когда оператор включается или отключается для владельца. Оператор может управлять
+# всеми NFT владельца.
+# @param _owner Владелец NFT.
+# @param _operator Адрес, которому мы устанавливаем права оператора.
+# @param _approved Статус прав оператора (true, если права оператора предоставлены, и false, если
+# они отозваны).
+event ApprovalForAll:
+ owner: indexed(address)
+ operator: indexed(address)
+ approved: bool
+```
+
+Иногда бывает полезно иметь _оператора_, который может управлять всеми токенами аккаунта определенного типа (теми, которые управляются
+определенным контрактом), подобно доверенности. Например, я могу захотеть предоставить такие полномочия контракту, который проверяет, не
+связывался ли я с ним в течение шести месяцев, и если да, то распределяет мои активы между моими наследниками (если один из них попросит об этом, контракты
+не могут ничего делать без вызова транзакцией). В ERC-20 мы можем просто дать высокое разрешение на контракт наследования,
+но это не работает для ERC-721, потому что токены не являются взаимозаменяемыми. Это эквивалент.
+
+Значение `approved` говорит нам о том, является ли событие одобрением или отзывом одобрения.
+
+### Переменные состояния {#state-vars}
+
+Эти переменные содержат текущее состояние токенов: какие из них доступны и кто ими владеет. Большинство из них
+это объекты `HashMap`, [однонаправленные сопоставления, которые существуют между двумя типами](https://vyper.readthedocs.io/en/latest/types.html#mappings).
+
+```python
+# @dev Сопоставление идентификатора NFT с адресом его владельца.
+idToOwner: HashMap[uint256, address]
+
+# @dev Сопоставление идентификатора NFT с одобренным адресом.
+idToApprovals: HashMap[uint256, address]
+```
+
+Идентификаторы пользователей и контрактов в Ethereum представлены 160-битными адресами. Эти две переменные сопоставляют
+идентификаторы токенов с их владельцами и теми, кто одобрен для их перевода (максимум по одному для каждого). В Ethereum
+неинициализированные данные всегда равны нулю, поэтому, если нет владельца или одобренного отправителя, значение для этого токена
+равно нулю.
+
+```python
+# @dev Сопоставление адреса владельца с количеством его токенов.
+ownerToNFTokenCount: HashMap[address, uint256]
+```
+
+Эта переменная содержит количество токенов для каждого владельца. Сопоставления от владельцев к токенам не существует, поэтому
+единственный способ идентифицировать токены, которыми владеет конкретный владелец, — это просмотреть историю событий блокчейна
+и найти соответствующие события `Transfer`. Мы можем использовать эту переменную, чтобы знать, когда у нас есть все NFT и не
+нужно заглядывать еще дальше во времени.
+
+Обратите внимание, что этот алгоритм работает только для пользовательских интерфейсов и внешних серверов. Код, работающий в блокчейне,
+сам по себе не может читать прошлые события.
+
+```python
+# @dev Сопоставление адреса владельца с сопоставлением адресов операторов.
+ownerToOperators: HashMap[address, HashMap[address, bool]]
+```
+
+У аккаунта может быть несколько операторов. Простого `HashMap` недостаточно, чтобы
+отслеживать их, потому что каждый ключ ведет к одному значению. Вместо этого вы можете использовать
+`HashMap[address, bool]` в качестве значения. По умолчанию значение для каждого адреса — `False`, что означает, что он
+не является оператором. Вы можете установить значения на `True` по мере необходимости.
+
+```python
+# @dev Адрес минтера, который может создавать токен
+minter: address
+```
+
+Новые токены должны как-то создаваться. В этом контракте есть единственная сущность, которой разрешено это делать, —
+`минтер`. Этого, например, скорее всего, будет достаточно для игры. Для других целей может потребоваться
+создать более сложную бизнес-логику.
+
+```python
+# @dev Сопоставление идентификатора интерфейса с логическим значением о том, поддерживается он или нет
+supportedInterfaces: HashMap[bytes32, bool]
+
+# @dev Идентификатор интерфейса ERC165 для ERC165
+ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
+
+# @dev Идентификатор интерфейса ERC165 для ERC721
+ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd
+```
+
+[ERC-165](https://eips.ethereum.org/EIPS/eip-165) определяет механизм, с помощью которого контракт может раскрывать, как приложения
+могут с ним взаимодействовать, каким стандартам ERC он соответствует. В данном случае контракт соответствует стандартам ERC-165 и ERC-721.
+
+### Функции {#functions}
+
+Это функции, которые фактически реализуют ERC-721.
+
+#### Конструктор {#constructor}
+
+```python
+@external
+def __init__():
+```
+
+В Vyper, как и в Python, функция-конструктор называется `__init__`.
+
+```python
+ """
+ @dev Конструктор контракта.
+ """
+```
+
+В Python и Vyper вы также можете создать комментарий, указав многострочную строку (которая начинается и заканчивается
+`"""`) и никак ее не используя. Эти комментарии также могут включать
+[NatSpec](https://vyper.readthedocs.io/en/latest/natspec.html).
+
+```python
+ self.supportedInterfaces[ERC165_INTERFACE_ID] = True
+ self.supportedInterfaces[ERC721_INTERFACE_ID] = True
+ self.minter = msg.sender
+```
+
+Для доступа к переменным состояния используйте `self.<имя переменной>`` (опять же, как и в Python).
+
+#### Функции просмотра {#views}
+
+Это функции, которые не изменяют состояние блокчейна и поэтому могут выполняться
+бесплатно, если вызываются извне. Если функции просмотра вызываются контрактом, они все равно должны выполняться на
+каждом узле и, следовательно, стоят газа.
+
+```python
+@view
+@external
+```
+
+Эти ключевые слова перед определением функции, которые начинаются со знака «собачка» (`@`), называются _декораторами_. Они
+указывают обстоятельства, при которых может быть вызвана функция.
+
+- `@view` указывает, что эта функция является функцией просмотра.
+- `@external` указывает, что данная функция может быть вызвана транзакциями и другими контрактами.
+
+```python
+def supportsInterface(_interfaceID: bytes32) -> bool:
+```
+
+В отличие от Python, Vyper — это [язык со статической типизацией](https://wikipedia.org/wiki/Type_system#Static_type_checking).
+Нельзя объявить переменную или параметр функции, не указав [тип данных](https://vyper.readthedocs.io/en/latest/types.html). В данном случае входной параметр — `bytes32`, 256-битное значение
+(256 бит — это нативный размер слова [виртуальной машины Ethereum](/developers/docs/evm/)). Выходные данные — это логическое
+значение. По соглашению имена параметров функции начинаются с символа подчеркивания (`_`).
+
+```python
+ """
+ @dev Идентификация интерфейса указана в ERC-165.
+ @param _interfaceID Идентификатор интерфейса
+ """
+ return self.supportedInterfaces[_interfaceID]
+```
+
+Возвращает значение из HashMap `self.supportedInterfaces`, которое устанавливается в конструкторе (`__init__`).
+
+```python
+### ФУНКЦИИ ПРОСМОТРА ###
+
+```
+
+Это функции просмотра, которые делают информацию о токенах доступной для пользователей и других контрактов.
+
+```python
+@view
+@external
+def balanceOf(_owner: address) -> uint256:
+ """
+ @dev Возвращает количество NFT, принадлежащих `_owner`.
+ Вызывает исключение, если `_owner` является нулевым адресом. NFT, назначенные нулевому адресу, считаются недействительными.
+ @param _owner Адрес, для которого запрашивается баланс.
+ """
+ assert _owner != ZERO_ADDRESS
+```
+
+Эта строка [проверяет](https://vyper.readthedocs.io/en/latest/statements.html#assert), что `_owner` не
+равен нулю. Если это так, возникает ошибка и операция отменяется.
+
+```python
+ return self.ownerToNFTokenCount[_owner]
+
+@view
+@external
+def ownerOf(_tokenId: uint256) -> address:
+ """
+ @dev Возвращает адрес владельца NFT.
+ Вызывает исключение, если `_tokenId` не является действительным NFT.
+ @param _tokenId Идентификатор для NFT.
+ """
+ owner: address = self.idToOwner[_tokenId]
+ # Вызывает исключение, если `_tokenId` не является действительным NFT
+ assert owner != ZERO_ADDRESS
+ return owner
+```
+
+В виртуальной машине Ethereum (EVM) любое хранилище, в котором не хранится значение, равно нулю.
+Если по адресу `_tokenId` нет токена, то значение `self.idToOwner[_tokenId]` равно нулю. В этом
+случае функция отменяется.
+
+```python
+@view
+@external
+def getApproved(_tokenId: uint256) -> address:
+ """
+ @dev Получает одобренный адрес для одного NFT.
+ Вызывает исключение, если `_tokenId` не является действительным NFT.
+ @param _tokenId Идентификатор NFT для запроса его одобрения.
+ """
+ # Вызывает исключение, если `_tokenId` не является действительным NFT
+ assert self.idToOwner[_tokenId] != ZERO_ADDRESS
+ return self.idToApprovals[_tokenId]
+```
+
+Обратите внимание, что `getApproved` _может_ вернуть ноль. Если токен действителен, он возвращает `self.idToApprovals[_tokenId]`.
+Если нет утверждающего, это значение равно нулю.
+
+```python
+@view
+@external
+def isApprovedForAll(_owner: address, _operator: address) -> bool:
+ """
+ @dev Проверяет, является ли `_operator` одобренным оператором для `_owner`.
+ @param _owner Адрес, которому принадлежат NFT.
+ @param _operator Адрес, который действует от имени владельца.
+ """
+ return (self.ownerToOperators[_owner])[_operator]
+```
+
+Эта функция проверяет, разрешено ли `_operator` управлять всеми токенами `_owner` в этом контракте.
+Поскольку операторов может быть несколько, это двухуровневый HashMap.
+
+#### Вспомогательные функции перевода {#transfer-helpers}
+
+Эти функции реализуют операции, которые являются частью перевода или управления токенами.
+
+```python
+
+### ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ПЕРЕВОДА ###
+
+@view
+@internal
+```
+
+Этот декоратор `@internal` означает, что функция доступна только из других функций в том
+же контракте. По соглашению, имена этих функций также начинаются с символа подчеркивания (`_`).
+
+```python
+def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
+ """
+ @dev Возвращает, может ли данный тратящий перевести данный идентификатор токена
+ @param spender адрес тратящего для запроса
+ @param tokenId uint256 ID токена для перевода
+ @return bool, является ли msg.sender одобренным для данного ID токена,
+ является ли оператором владельца, или является владельцем токена
+ """
+ owner: address = self.idToOwner[_tokenId]
+ spenderIsOwner: bool = owner == _spender
+ spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
+ spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
+ return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
+```
+
+Существует три способа, которыми адрес может быть разрешен для перевода токена:
+
+1. Адрес является владельцем токена
+2. Адрес одобрен для расходования этого токена
+3. Адрес является оператором для владельца токена
+
+Вышеупомянутая функция может быть функцией просмотра, поскольку она не изменяет состояние. Чтобы снизить эксплуатационные расходы, любая
+функция, которая _может_ быть функцией просмотра, _должна_ ею быть.
+
+```python
+@internal
+def _addTokenTo(_to: address, _tokenId: uint256):
+ """
+ @dev Добавляет NFT на данный адрес
+ Вызывает исключение, если `_tokenId` принадлежит кому-то.
+ """
+ # Вызывает исключение, если `_tokenId` принадлежит кому-то
+ assert self.idToOwner[_tokenId] == ZERO_ADDRESS
+ # Изменение владельца
+ self.idToOwner[_tokenId] = _to
+ # Изменение отслеживания количества
+ self.ownerToNFTokenCount[_to] += 1
+
+
+@internal
+def _removeTokenFrom(_from: address, _tokenId: uint256):
+ """
+ @dev Удаляет NFT с данного адреса
+ Вызывает исключение, если `_from` не является текущим владельцем.
+ """
+ # Вызывает исключение, если `_from` не является текущим владельцем
+ assert self.idToOwner[_tokenId] == _from
+ # Изменение владельца
+ self.idToOwner[_tokenId] = ZERO_ADDRESS
+ # Изменение отслеживания количества
+ self.ownerToNFTokenCount[_from] -= 1
+```
+
+Когда возникает проблема с переводом, мы отменяем вызов.
+
+```python
+@internal
+def _clearApproval(_owner: address, _tokenId: uint256):
+ """
+ @dev Очищает одобрение для данного адреса
+ Вызывает исключение, если `_owner` не является текущим владельцем.
+ """
+ # Вызывает исключение, если `_owner` не является текущим владельцем
+ assert self.idToOwner[_tokenId] == _owner
+ if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
+ # Сброс одобрений
+ self.idToApprovals[_tokenId] = ZERO_ADDRESS
+```
+
+Изменяйте значение только при необходимости. Переменные состояния находятся в хранилище. Запись в хранилище —
+одна из самых дорогих операций, которые выполняет EVM (виртуальная машина Ethereum) (с точки зрения
+[газа](/developers/docs/gas/)). Поэтому хорошей идеей является ее минимизация, даже запись
+существующего значения имеет высокую стоимость.
+
+```python
+@internal
+def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
+ """
+ @dev Выполнение перевода NFT.
+ Вызывает исключение, если `msg.sender` не является текущим владельцем, авторизованным оператором или одобренным
+ адресом для этого NFT. (ПРИМЕЧАНИЕ: `msg.sender` не разрешен в приватной функции, поэтому передайте `_sender`.)
+ Вызывает исключение, если `_to` является нулевым адресом.
+ Вызывает исключение, если `_from` не является текущим владельцем.
+ Вызывает исключение, если `_tokenId` не является действительным NFT.
+ """
+```
+
+У нас есть эта внутренняя функция, потому что существует два способа перевода токенов (обычный и безопасный), но
+мы хотим, чтобы это делалось только в одном месте кода, чтобы облегчить аудит.
+
+```python
+ # Проверка требований
+ assert self._isApprovedOrOwner(_sender, _tokenId)
+ # Вызывает исключение, если `_to` является нулевым адресом
+ assert _to != ZERO_ADDRESS
+ # Очистка одобрения. Вызывает исключение, если `_from` не является текущим владельцем
+ self._clearApproval(_from, _tokenId)
+ # Удаление NFT. Вызывает исключение, если `_tokenId` не является действительным NFT
+ self._removeTokenFrom(_from, _tokenId)
+ # Добавление NFT
+ self._addTokenTo(_to, _tokenId)
+ # Логирование перевода
+ log Transfer(_from, _to, _tokenId)
+```
+
+Чтобы сгенерировать событие в Vyper, используется оператор `log` ([подробнее см. здесь](https://vyper.readthedocs.io/en/latest/event-logging.html#event-logging)).
+
+#### Функции перевода {#transfer-funs}
+
+```python
+
+### ФУНКЦИИ ПЕРЕВОДА ###
+
+@external
+def transferFrom(_from: address, _to: address, _tokenId: uint256):
+ """
+ @dev Вызывает исключение, если `msg.sender` не является текущим владельцем, авторизованным оператором или одобренным
+ адресом для этого NFT.
+ Вызывает исключение, если `_from` не является текущим владельцем.
+ Вызывает исключение, если `_to` является нулевым адресом.
+ Вызывает исключение, если `_tokenId` не является действительным NFT.
+ @notice Вызывающий несет ответственность за подтверждение того, что `_to` способен получать NFT, иначе
+ они могут быть навсегда потеряны.
+ @param _from Текущий владелец NFT.
+ @param _to Новый владелец.
+ @param _tokenId NFT для перевода.
+ """
+ self._transferFrom(_from, _to, _tokenId, msg.sender)
+```
+
+Эта функция позволяет выполнять перевод на произвольный адрес. Если адрес не является адресом пользователя или контракта, который
+знает, как переводить токены, любой переведенный вами токен застрянет на этом адресе и станет бесполезным.
+
+```python
+@external
+def safeTransferFrom(
+ _from: address,
+ _to: address,
+ _tokenId: uint256,
+ _data: Bytes[1024]=b""
+ ):
+ """
+ @dev Передает право собственности на NFT с одного адреса на другой.
+ Вызывает исключение, если `msg.sender` не является текущим владельцем, авторизованным оператором или
+ одобренным адресом для этого NFT.
+ Вызывает исключение, если `_from` не является текущим владельцем.
+ Вызывает исключение, если `_to` — нулевой адрес.
+ Вызывает исключение, если `_tokenId` не является действительным NFT.
+ Если `_to` — это смарт-контракт, он вызывает `onERC721Received` для `_to` и вызывает исключение, если
+ возвращаемое значение не равно `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
+ ПРИМЕЧАНИЕ: bytes4 представлен как bytes32 с дополнением
+ @param _from Текущий владелец NFT.
+ @param _to Новый владелец.
+ @param _tokenId NFT для перевода.
+ @param _data Дополнительные данные без определенного формата, отправляемые при вызове `_to`.
+ """
+ self._transferFrom(_from, _to, _tokenId, msg.sender)
+```
+
+Можно сначала выполнить перевод, потому что если возникнет проблема, мы все равно отменим операцию,
+так что все, что было сделано в вызове, будет отменено.
+
+```python
+ if _to.is_contract: # проверяем, является ли `_to` адресом контракта
+```
+
+Сначала проверьте, является ли адрес контрактом (есть ли у него код). Если нет, предположим, что это адрес пользователя,
+и пользователь сможет использовать токен или перевести его. Но не позволяйте этому усыпить вашу
+бдительность. Вы можете потерять токены, даже с `safeTransferFrom`, если переведете
+их на адрес, для которого никто не знает приватный ключ.
+
+```python
+ returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
+```
+
+Вызовите целевой контракт, чтобы узнать, может ли он получать токены ERC-721.
+
+```python
+ # Вызывает исключение, если получатель перевода — это контракт, который не реализует 'onERC721Received'
+ assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)
+```
+
+Если получатель — это контракт, но он не принимает токены ERC-721 (или решил не принимать этот
+конкретный перевод), отмените операцию.
+
+```python
+@external
+def approve(_approved: address, _tokenId: uint256):
+ """
+ @dev Устанавливает или подтверждает одобренный адрес для NFT. Нулевой адрес указывает на отсутствие одобренного адреса.
+ Вызывает исключение, если `msg.sender` не является текущим владельцем NFT или авторизованным оператором текущего владельца.
+ Вызывает исключение, если `_tokenId` не является действительным NFT. (ПРИМЕЧАНИЕ: этого нет в EIP)
+ Вызывает исключение, если `_approved` является текущим владельцем. (ПРИМЕЧАНИЕ: этого нет в EIP)
+ @param _approved Адрес, который должен быть одобрен для данного идентификатора NFT.
+ @param _tokenId Идентификатор токена, который должен быть одобрен.
+ """
+ owner: address = self.idToOwner[_tokenId]
+ # Вызывает исключение, если `_tokenId` не является действительным NFT
+ assert owner != ZERO_ADDRESS
+ # Вызывает исключение, если `_approved` является текущим владельцем
+ assert _approved != owner
+```
+
+По соглашению, если вы не хотите иметь утверждающего, вы назначаете нулевой адрес, а не себя.
+
+```python
+ # Проверка требований
+ senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
+ senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
+ assert (senderIsOwner or senderIsApprovedForAll)
+```
+
+Чтобы установить одобрение, вы можете быть либо владельцем, либо оператором, уполномоченным владельцем.
+
+```python
+ # Установка одобрения
+ self.idToApprovals[_tokenId] = _approved
+ log Approval(owner, _approved, _tokenId)
+
+
+@external
+def setApprovalForAll(_operator: address, _approved: bool):
+ """
+ @dev Включает или отключает одобрение для третьей стороны ("оператора") для управления всеми
+ активами `msg.sender`. Также генерирует событие ApprovalForAll.
+ Вызывает исключение, если `_operator` является `msg.sender`. (ПРИМЕЧАНИЕ: этого нет в EIP)
+ @notice Это работает, даже если отправитель не владеет никакими токенами в данный момент.
+ @param _operator Адрес для добавления в набор авторизованных операторов.
+ @param _approved True, если оператор одобрен, false — для отзыва одобрения.
+ """
+ # Вызывает исключение, если `_operator` является `msg.sender`
+ assert _operator != msg.sender
+ self.ownerToOperators[msg.sender][_operator] = _approved
+ log ApprovalForAll(msg.sender, _operator, _approved)
+```
+
+#### Создание новых токенов и уничтожение существующих {#mint-burn}
+
+Аккаунт, создавший контракт, является `минтером` — суперпользователем, уполномоченным создавать
+новые NFT. Однако даже ему не разрешается сжигать существующие токены. Это может сделать только владелец или уполномоченное им лицо.
+
+```python
+### ФУНКЦИИ СОЗДАНИЯ И СЖИГАНИЯ ###
+
+@external
+def mint(_to: address, _tokenId: uint256) -> bool:
+```
+
+Эта функция всегда возвращает `True`, потому что в случае сбоя операция отменяется.
+
+```python
+ """
+ @dev Функция для создания токенов
+ Вызывает исключение, если `msg.sender` не является минтером.
+ Вызывает исключение, если `_to` является нулевым адресом.
+ Вызывает исключение, если `_tokenId` уже принадлежит кому-то.
+ @param _to Адрес, который получит созданные токены.
+ @param _tokenId Идентификатор токена для создания.
+ @return Логическое значение, которое указывает, была ли операция успешной.
+ """
+ # Вызывает исключение, если `msg.sender` не является минтером
+ assert msg.sender == self.minter
+```
+
+Только минтер (аккаунт, создавший контракт ERC-721) может создавать новые токены. Это может
+стать проблемой в будущем, если мы захотим изменить идентификатор минтера. В
+рабочем контракте, вероятно, понадобится функция, позволяющая минтеру передавать
+привилегии минтера кому-то другому.
+
+```python
+ # Вызывает исключение, если `_to` является нулевым адресом
+ assert _to != ZERO_ADDRESS
+ # Добавление NFT. Вызывает исключение, если `_tokenId` уже принадлежит кому-то
+ self._addTokenTo(_to, _tokenId)
+ log Transfer(ZERO_ADDRESS, _to, _tokenId)
+ return True
+```
+
+По соглашению, создание новых токенов считается переводом с нулевого адреса.
+
+```python
+
+@external
+def burn(_tokenId: uint256):
+ """
+ @dev Сжигает определенный токен ERC721.
+ Вызывает исключение, если `msg.sender` не является текущим владельцем, авторизованным оператором или одобренным
+ адресом для этого NFT.
+ Вызывает исключение, если `_tokenId` не является действительным NFT.
+ @param _tokenId uint256 id токена ERC721 для сжигания.
+ """
+ # Проверка требований
+ assert self._isApprovedOrOwner(msg.sender, _tokenId)
+ owner: address = self.idToOwner[_tokenId]
+ # Вызывает исключение, если `_tokenId` не является действительным NFT
+ assert owner != ZERO_ADDRESS
+ self._clearApproval(owner, _tokenId)
+ self._removeTokenFrom(owner, _tokenId)
+ log Transfer(owner, ZERO_ADDRESS, _tokenId)
+```
+
+Любой, кому разрешено переводить токен, может его сжечь. Хотя сжигание кажется эквивалентным
+переводу на нулевой адрес, нулевой адрес на самом деле не получает токен. Это позволяет нам
+освободить все хранилище, которое использовалось для токена, что может снизить стоимость газа транзакции.
+
+## Использование этого контракта {#using-contract}
+
+В отличие от Solidity, в Vyper нет наследования. Это сознательное проектное решение, чтобы сделать
+код более ясным и, следовательно, более простым в обеспечении безопасности. Поэтому для создания собственного контракта Vyper ERC-721 вы берете этот
+контракт и изменяете его
+для реализации желаемой бизнес-логики.
+
+## Заключение {#conclusion}
+
+Для обзора, вот некоторые из наиболее важных идей в этом контракте:
+
+- Для получения токенов ERC-721 с помощью безопасного перевода контракты должны реализовывать интерфейс `ERC721Receiver`.
+- Даже если вы используете безопасный перевод, токены все равно могут застрять, если вы отправите их на адрес, чей приватный ключ
+ неизвестен.
+- При возникновении проблемы с операцией рекомендуется `отменить` вызов, а не просто возвращать
+ значение сбоя.
+- Токены ERC-721 существуют, когда у них есть владелец.
+- Есть три способа получить разрешение на передачу NFT. Вы можете быть владельцем, иметь одобрение для конкретного токена
+ или быть оператором для всех токенов владельца.
+- Прошлые события видны только за пределами блокчейна. Код, работающий внутри блокчейна, не может их просматривать.
+
+А теперь идите и реализуйте безопасные контракты Vyper.
+
+[Больше моих работ смотрите здесь](https://cryptodocguy.pro/).
+
diff --git a/public/content/translations/ru/developers/tutorials/erc20-annotated-code/index.md b/public/content/translations/ru/developers/tutorials/erc20-annotated-code/index.md
new file mode 100644
index 00000000000..6de55ddb360
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/erc20-annotated-code/index.md
@@ -0,0 +1,932 @@
+---
+title: "Пошаговый разбор контракта ERC-20"
+description: "Что содержит контракт ERC-20 от OpenZeppelin и для чего это нужно?"
+author: Ori Pomerantz
+lang: ru
+tags: [ "твердость", "erc-20" ]
+skill: beginner
+published: 2021-03-09
+---
+
+## Введение {#introduction}
+
+Чаще всего Ethereum используется для создания собственного токена, который можно использовать как валюту. Эти токены обычно следуют стандарту,
+[ERC-20](/developers/docs/standards/tokens/erc-20/). Этот стандарт позволяет создавать такие инструменты, как пулы ликвидности и кошельки, которые работают со всеми токенами
+ERC-20. В этой статье мы проанализируем
+[реализацию ERC20 на Solidity от OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol), а также
+[определение интерфейса](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol).
+
+Это аннотированный исходный код. Если вы хотите реализовать ERC-20,
+[прочтите это руководство](https://docs.openzeppelin.com/contracts/2.x/erc20-supply).
+
+## Интерфейс {#the-interface}
+
+Цель такого стандарта, как ERC-20, — обеспечить совместимость множества реализаций токенов в различных приложениях, таких как кошельки и децентрализованные биржи. Для этого мы создаем
+[интерфейс](https://www.geeksforgeeks.org/solidity/solidity-basics-of-interface/). Любой код, которому требуется использовать контракт токена,
+может использовать те же определения в интерфейсе и быть совместимым со всеми контрактами токенов, которые его используют, будь то кошелек, такой как
+MetaMask, децентрализованное приложение, такое как etherscan.io, или другой контракт, например пул ликвидности.
+
+
+
+Если вы опытный программист, вы, вероятно, помните, что видели подобные конструкции в [Java](https://www.w3schools.com/java/java_interface.asp)
+или даже в [заголовочных файлах C](https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html).
+
+Это определение [интерфейса ERC-20](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol)
+от OpenZeppelin. Это перевод [человекочитаемого стандарта](https://eips.ethereum.org/EIPS/eip-20) в код Solidity. Конечно, сам
+интерфейс не определяет, _как_ что-либо делать. Это объясняется в исходном коде контракта ниже.
+
+
+
+```solidity
+// SPDX-License-Identifier: MIT
+```
+
+Файлы Solidity должны содержать идентификатор лицензии. [Вы можете посмотреть список лицензий здесь](https://spdx.org/licenses/). Если вам нужна другая
+лицензия, просто укажите это в комментариях.
+
+
+
+```solidity
+pragma solidity >=0.6.0 <0.8.0;
+```
+
+Язык Solidity все еще быстро развивается, и новые версии могут быть несовместимы со старым кодом
+([см. здесь](https://docs.soliditylang.org/en/v0.7.0/070-breaking-changes.html)). Поэтому рекомендуется указывать не только минимальную
+версию языка, но и максимальную — последнюю, с которой вы тестировали код.
+
+
+
+```solidity
+/**
+ * @dev Интерфейс стандарта ERC20, как определено в EIP.
+ */
+```
+
+`@dev` в комментарии является частью [формата NatSpec](https://docs.soliditylang.org/en/develop/natspec-format.html), используемого для создания
+документации из исходного кода.
+
+
+
+```solidity
+interface IERC20 {
+```
+
+По соглашению, имена интерфейсов начинаются с `I`.
+
+
+
+```solidity
+ /**
+ * @dev Возвращает количество существующих токенов.
+ */
+ function totalSupply() external view returns (uint256);
+```
+
+Эта функция является `external`, что означает, [что она может быть вызвана только извне контракта](https://docs.soliditylang.org/en/v0.7.0/cheatsheet.html#index-2).
+Она возвращает общее количество токенов в контракте. Это значение возвращается с использованием самого распространенного типа в Ethereum, 256-битного беззнакового целого числа (256 бит — это
+собственный размер слова EVM). Эта функция также является `view`, что означает, что она не изменяет состояние, поэтому ее можно выполнить на одном узле, вместо того чтобы ее
+выполнял каждый узел в блокчейне. Такая функция не создает транзакцию и не требует [Газа](/developers/docs/gas/).
+
+**Примечание.** Теоретически может показаться, что создатель контракта может сжульничать, возвращая общее предложение меньше реального, чтобы каждый токен казался
+более ценным, чем он есть на самом деле. Однако этот страх игнорирует истинную природу блокчейна. Все, что происходит в блокчейне, может быть проверено
+каждым узлом. Для этого машинный код и хранилище каждого контракта доступны на каждом узле. Хотя вы не обязаны публиковать код
+Solidity для вашего контракта, никто не воспримет вас всерьез, если вы не опубликуете исходный код и версию Solidity, с которой он был скомпилирован, чтобы его можно было
+проверить на соответствие предоставленному вами машинному коду.
+Например, см. [этот контракт](https://eth.blockscout.com/address/0xa530F85085C6FE2f866E7FdB716849714a89f4CD?tab=contract).
+
+
+
+```solidity
+ /**
+ * @dev Возвращает количество токенов, принадлежащих `account`.
+ */
+ function balanceOf(address account) external view returns (uint256);
+```
+
+Как следует из названия, `balanceOf` возвращает баланс аккаунта. Аккаунты Ethereum идентифицируются в Solidity с помощью типа `address`, который содержит 160 бит.
+Она также `external` и `view`.
+
+
+
+```solidity
+ /**
+ * @dev Перемещает `amount` токенов со счета вызывающего на `recipient`.
+ *
+ * Возвращает логическое значение, указывающее, успешно ли выполнена операция.
+ *
+ * Инициирует событие {Transfer}.
+ */
+ function transfer(address recipient, uint256 amount) external returns (bool);
+```
+
+Функция `transfer` переводит токены с вызывающего адреса на другой адрес. Это связано с изменением состояния, поэтому она не является `view`.
+Когда пользователь вызывает эту функцию, создается транзакция и расходуется Газ. Она также инициирует событие `Transfer`, чтобы проинформировать всех в
+блокчейне об этом событии.
+
+Функция имеет два типа вывода для двух разных типов вызывающих:
+
+- Пользователи, которые вызывают функцию напрямую из пользовательского интерфейса. Обычно пользователь отправляет транзакцию
+ и не ждет ответа, что может занять неопределенное количество времени. Пользователь может увидеть, что произошло,
+ поискав квитанцию о транзакции (которая идентифицируется по Хэшу транзакции) или поискав событие
+ `Transfer`.
+- Другие контракты, которые вызывают функцию как часть общей транзакции. Эти контракты получают результат немедленно,
+ потому что они выполняются в той же транзакции, поэтому они могут использовать возвращаемое функцией значение.
+
+Тот же тип вывода создается и другими функциями, которые изменяют состояние контракта.
+
+
+
+Разрешения позволяют аккаунту тратить некоторые токены, принадлежащие другому владельцу.
+Это полезно, например, для контрактов, которые действуют как продавцы. Контракты не могут
+отслеживать события, поэтому, если покупатель переведет токены на контракт продавца
+напрямую, этот контракт не узнает, что ему заплатили. Вместо этого покупатель разрешает
+контракту продавца потратить определенную сумму, и продавец переводит эту сумму.
+Это делается с помощью функции, которую вызывает контракт продавца, поэтому контракт продавца
+может узнать, была ли операция успешной.
+
+```solidity
+ /**
+ * @dev Возвращает оставшееся количество токенов, которые `spender` сможет
+ * потратить от имени `owner` через {transferFrom}. По умолчанию
+ * это ноль.
+ *
+ * Это значение изменяется при вызове {approve} или {transferFrom}.
+ */
+ function allowance(address owner, address spender) external view returns (uint256);
+```
+
+Функция `allowance` позволяет любому запросить, какое разрешение один
+адрес (`owner`) дает другому адресу (`spender`) на трату.
+
+
+
+```solidity
+ /**
+ * @dev Устанавливает `amount` в качестве разрешенной суммы для `spender` сверх токенов вызывающей стороны.
+ *
+ * Возвращает логическое значение, указывающее, успешно ли выполнена операция.
+ *
+ * ВАЖНО: имейте в виду, что изменение разрешения с помощью этого метода несет в себе риск,
+ * что кто-то может использовать и старое, и новое разрешение из-за неудачного
+ * порядка транзакций. Одним из возможных решений для смягчения этого состояния гонки
+ * является сначала уменьшение разрешения для тратящего до 0, а затем установка
+ * желаемого значения:
+ * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
+ *
+ * Инициирует событие {Approval}.
+ */
+ function approve(address spender, uint256 amount) external returns (bool);
+```
+
+Функция `approve` создает разрешение. Обязательно прочтите сообщение о том,
+как это можно использовать не по назначению. В Ethereum вы контролируете порядок своих собственных транзакций,
+но не можете контролировать порядок, в котором будут выполняться транзакции
+других людей, если только вы не отправите свою собственную транзакцию после
+того, как увидите, что транзакция другой стороны произошла.
+
+
+
+```solidity
+ /**
+ * @dev Перемещает токены в размере `amount` от `sender` к `recipient` с использованием
+ * механизма разрешений. Затем `amount` вычитается из разрешения
+ * вызывающей стороны.
+ *
+ * Возвращает логическое значение, указывающее, успешно ли выполнена операция.
+ *
+ * Инициирует событие {Transfer}.
+ */
+ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
+```
+
+Наконец, `transferFrom` используется тратящим для фактического использования разрешения.
+
+
+
+```solidity
+
+ /**
+ * @dev Создается при перемещении `value` токенов с одного аккаунта (`from`) на
+ * другой (`to`).
+ *
+ * Обратите внимание, что `value` может быть равно нулю.
+ */
+ event Transfer(address indexed from, address indexed to, uint256 value);
+
+ /**
+ * @dev Создается, когда лимит для `spender` от `owner` устанавливается
+ * вызовом {approve}. `value` — это новый лимит.
+ */
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+```
+
+Эти события инициируются при изменении состояния контракта ERC-20.
+
+## Сам контракт {#the-actual-contract}
+
+Это фактический контракт, реализующий стандарт ERC-20,
+[взятый отсюда](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol).
+Он не предназначен для использования «как есть», но вы можете
+[наследоваться](https://www.tutorialspoint.com/solidity/solidity_inheritance.htm) от него, чтобы расширить его до чего-то пригодного для использования.
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.6.0 <0.8.0;
+```
+
+
+
+### Инструкции импорта {#import-statements}
+
+Помимо определений интерфейса выше, определение контракта импортирует два других файла:
+
+```solidity
+
+import "../../GSN/Context.sol";
+import "./IERC20.sol";
+import "../../math/SafeMath.sol";
+```
+
+- `GSN/Context.sol` — это определения, необходимые для использования [OpenGSN](https://www.opengsn.org/), системы, которая позволяет пользователям без
+ ether использовать блокчейн. Обратите внимание, что это старая версия, если вы хотите интегрироваться с OpenGSN,
+ [используйте это руководство](https://docs.opengsn.org/javascript-client/tutorial.html).
+- [Библиотека SafeMath](https://ethereumdev.io/using-safe-math-library-to-prevent-from-overflows/), которая предотвращает
+ арифметические переполнения/недополнения для версий Solidity **<0.8.0**. В Solidity ≥0.8.0 арифметические операции автоматически
+ отменяются при переполнении/недополнении, что делает SafeMath ненужной. Этот контракт использует SafeMath для обратной совместимости со
+ старыми версиями компилятора.
+
+
+
+Этот комментарий объясняет назначение контракта.
+
+```solidity
+/**
+ * @dev Реализация интерфейса {IERC20}.
+ *
+ * Эта реализация не зависит от способа создания токенов. Это означает,
+ * что механизм предоставления должен быть добавлен в производный контракт с использованием {_mint}.
+ * Общий механизм см. в {ERC20PresetMinterPauser}.
+ *
+ * СОВЕТ: для подробного описания см. наше руководство
+ * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[Как
+ * реализовать механизмы предоставления].
+ *
+ * Мы следовали общим рекомендациям OpenZeppelin: функции отменяются вместо
+ * возврата `false` при сбое. Такое поведение, тем не менее, является общепринятым
+ * и не противоречит ожиданиям приложений ERC20.
+ *
+ * Кроме того, при вызовах {transferFrom} инициируется событие {Approval}.
+ * Это позволяет приложениям восстанавливать разрешение для всех аккаунтов, просто
+ * прослушивая указанные события. Другие реализации EIP могут не инициировать
+ * эти события, поскольку это не требуется спецификацией.
+ *
+ * Наконец, были добавлены нестандартные функции {decreaseAllowance} и {increaseAllowance}
+ * для смягчения известных проблем, связанных с установкой
+ * разрешений. См. {IERC20-approve}.
+ */
+
+```
+
+### Определение контракта {#contract-definition}
+
+```solidity
+contract ERC20 is Context, IERC20 {
+```
+
+Эта строка определяет наследование, в данном случае от `IERC20` выше и `Context` для OpenGSN.
+
+
+
+```solidity
+
+ using SafeMath for uint256;
+
+```
+
+Эта строка присоединяет библиотеку `SafeMath` к типу `uint256`. Вы можете найти эту библиотеку
+[здесь](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol).
+
+### Определения переменных {#variable-definitions}
+
+Эти определения определяют переменные состояния контракта. Эти переменные объявлены `private`, но
+это означает только то, что другие контракты в блокчейне не могут их читать. _В блокчейне нет
+секретов_, программное обеспечение на каждом узле имеет состояние каждого контракта
+в каждом блоке. По соглашению переменные состояния именуются `_<что-то>`.
+
+Первые две переменные — это [сопоставления (mappings)](https://www.tutorialspoint.com/solidity/solidity_mappings.htm),
+что означает, что они ведут себя примерно так же, как [ассоциативные массивы](https://wikipedia.org/wiki/Associative_array),
+за исключением того, что ключи являются числовыми значениями. Хранилище выделяется только для записей, которые имеют значения, отличные
+от значения по умолчанию (ноль).
+
+```solidity
+ mapping (address => uint256) private _balances;
+```
+
+Первое сопоставление, `_balances`, — это адреса и их соответствующие балансы этого токена. Чтобы получить доступ к
+балансу, используйте этот синтаксис: `_balances[]`.
+
+
+
+```solidity
+ mapping (address => mapping (address => uint256)) private _allowances;
+```
+
+Эта переменная, `_allowances`, хранит разрешения, объясненные ранее. Первый индекс — это владелец
+токенов, а второй — контракт с разрешением. Чтобы получить доступ к сумме, которую адрес A может
+потратить со счета адреса B, используйте `_allowances[B][A]`.
+
+
+
+```solidity
+ uint256 private _totalSupply;
+```
+
+Как следует из названия, эта переменная отслеживает общее предложение токенов.
+
+
+
+```solidity
+ string private _name;
+ string private _symbol;
+ uint8 private _decimals;
+```
+
+Эти три переменные используются для улучшения читаемости. Первые две говорят сами за себя, а вот `_decimals`
+— нет.
+
+С одной стороны, в Ethereum нет переменных с плавающей запятой или дробных переменных. С другой стороны,
+людям нравится иметь возможность делить токены. Одна из причин, по которой люди остановились на золоте в качестве валюты, заключалась в том, что
+было трудно разменивать деньги, когда кто-то хотел купить утку за часть коровы.
+
+Решение состоит в том, чтобы отслеживать целые числа, но считать не реальный токен, а дробный токен, который
+почти ничего не стоит. В случае с ether дробный токен называется wei, и 10^18 wei равны одному
+ETH. На момент написания 10 000 000 000 000 wei примерно равны одному центу США или евро.
+
+Приложениям необходимо знать, как отображать баланс токена. Если у пользователя 3 141 000 000 000 000 000 wei, это
+3,14 ETH? 31,41 ETH? 3141 ETH? В случае с ether определено 10^18 wei на ETH, но для вашего
+токена вы можете выбрать другое значение. Если деление токена не имеет смысла, вы можете использовать
+значение `_decimals`, равное нулю. Если вы хотите использовать тот же стандарт, что и для ETH, используйте значение **18**.
+
+### Конструктор {#the-constructor}
+
+```solidity
+ /**
+ * @dev Устанавливает значения для {name} и {symbol}, инициализирует {decimals} со
+ * значением по умолчанию 18.
+ *
+ * Чтобы выбрать другое значение для {decimals}, используйте {_setupDecimals}.
+ *
+ * Все три этих значения неизменяемы: их можно установить только один раз во время
+ * создания.
+ */
+ constructor (string memory name_, string memory symbol_) public {
+ // В Solidity ≥0.7.0 'public' является неявным и может быть опущен.
+
+ _name = name_;
+ _symbol = symbol_;
+ _decimals = 18;
+ }
+```
+
+Конструктор вызывается при первом создании контракта. По соглашению параметры функции именуются `<что-то>_`.
+
+### Функции пользовательского интерфейса {#user-interface-functions}
+
+```solidity
+ /**
+ * @dev Возвращает имя токена.
+ */
+ function name() public view returns (string memory) {
+ return _name;
+ }
+
+ /**
+ * @dev Возвращает символ токена, обычно более короткую версию
+ * имени.
+ */
+ function symbol() public view returns (string memory) {
+ return _symbol;
+ }
+
+ /**
+ * @dev Возвращает количество десятичных знаков, используемых для получения его представления для пользователя.
+ * Например, если `decimals` равно `2`, баланс `505` токенов должен
+ * отображаться пользователю как `5,05` (`505 / 10 ** 2`).
+ *
+ * Токены обычно выбирают значение 18, имитируя отношение между
+ * ether и wei. Это значение, которое использует {ERC20}, если не вызывается {_setupDecimals}.
+ *
+ * ПРИМЕЧАНИЕ: Эта информация используется только для _отображения_: она ни в коем
+ * случае не влияет на арифметику контракта, включая
+ * {IERC20-balanceOf} и {IERC20-transfer}.
+ */
+ function decimals() public view returns (uint8) {
+ return _decimals;
+ }
+```
+
+Эти функции, `name`, `symbol` и `decimals`, помогают пользовательским интерфейсам узнать о вашем контракте, чтобы они могли правильно его отображать.
+
+Возвращаемый тип — `string memory`, что означает возврат строки, которая хранится в памяти. Переменные, такие как
+строки, могут храниться в трех местах:
+
+| | Время жизни | Доступ к контракту | Стоимость Газа |
+| --------- | ------------- | ------------------ | ---------------------------------------------------------------------------------------- |
+| Память | Вызов функции | Чтение/запись | Десятки или сотни (больше для более высоких местоположений) |
+| Calldata | Вызов функции | Только для чтения | Не может использоваться как тип возвращаемого значения, только как тип параметра функции |
+| Хранилище | До изменения | Чтение/запись | Высокая (800 для чтения, 20 тыс. для записи) |
+
+В данном случае `memory` — лучший выбор.
+
+### Чтение информации о токене {#read-token-information}
+
+Это функции, которые предоставляют информацию о токене: общее предложение или баланс
+аккаунта.
+
+```solidity
+ /**
+ * @dev См. {IERC20-totalSupply}.
+ */
+ function totalSupply() public view override returns (uint256) {
+ return _totalSupply;
+ }
+```
+
+Функция `totalSupply` возвращает общее предложение токенов.
+
+
+
+```solidity
+ /**
+ * @dev См. {IERC20-balanceOf}.
+ */
+ function balanceOf(address account) public view override returns (uint256) {
+ return _balances[account];
+ }
+```
+
+Чтение баланса аккаунта. Обратите внимание, что любой может получить баланс аккаунта
+любого другого. Нет смысла пытаться скрыть эту информацию, потому что она и так доступна на каждом
+узле. _В блокчейне нет секретов._
+
+### Перевод токенов {#transfer-tokens}
+
+```solidity
+ /**
+ * @dev См. {IERC20-transfer}.
+ *
+ * Требования:
+ *
+ * - `recipient` не может быть нулевым адресом.
+ * - у вызывающего должен быть баланс не менее `amount`.
+ */
+ function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
+```
+
+Функция `transfer` вызывается для перевода токенов со счета отправителя на другой. Обратите
+внимание, что хотя она и возвращает логическое значение, это значение всегда **true**. Если перевод
+не удается, контракт отменяет вызов.
+
+
+
+```solidity
+ _transfer(_msgSender(), recipient, amount);
+ return true;
+ }
+```
+
+Функция `_transfer` выполняет фактическую работу. Это приватная функция, которая может быть вызвана только
+другими функциями контракта. По соглашению приватные функции именуются `_<что-то>`, так же как и переменные
+состояния.
+
+Обычно в Solidity мы используем `msg.sender` для отправителя сообщения. Однако это нарушает работу
+[OpenGSN](http://opengsn.org/). Если мы хотим разрешить транзакции без ether с нашим токеном, нам
+нужно использовать `_msgSender()`. Она возвращает `msg.sender` для обычных транзакций, но для транзакций без ether
+возвращает исходного подписанта, а не контракт, который переслал сообщение.
+
+### Функции разрешений {#allowance-functions}
+
+Это функции, которые реализуют функциональность разрешений: `allowance`, `approve`, `transferFrom`
+и `_approve`. Кроме того, реализация OpenZeppelin выходит за рамки базового стандарта и включает некоторые функции, которые улучшают
+безопасность: `increaseAllowance` и `decreaseAllowance`.
+
+#### Функция allowance {#allowance}
+
+```solidity
+ /**
+ * @dev См. {IERC20-allowance}.
+ */
+ function allowance(address owner, address spender) public view virtual override returns (uint256) {
+ return _allowances[owner][spender];
+ }
+```
+
+Функция `allowance` позволяет всем проверять любое разрешение.
+
+#### Функция approve {#approve}
+
+```solidity
+ /**
+ * @dev См. {IERC20-approve}.
+ *
+ * Требования:
+ *
+ * - `spender` не может быть нулевым адресом.
+ */
+ function approve(address spender, uint256 amount) public virtual override returns (bool) {
+```
+
+Эта функция вызывается для создания разрешения. Она похожа на функцию `transfer` выше:
+
+- Функция просто вызывает внутреннюю функцию (в данном случае `_approve`), которая выполняет реальную работу.
+- Функция либо возвращает `true` (в случае успеха), либо отменяет операцию (в противном случае).
+
+
+
+```solidity
+ _approve(_msgSender(), spender, amount);
+ return true;
+ }
+```
+
+Мы используем внутренние функции, чтобы минимизировать количество мест, где происходят изменения состояния. _Любая_ функция, изменяющая
+состояние, является потенциальным риском безопасности, который необходимо проверить на безопасность. Таким образом, у нас меньше шансов ошибиться.
+
+#### Функция transferFrom {#transferFrom}
+
+Это функция, которую тратящий вызывает для использования разрешения. Это требует двух операций: перевода потраченной суммы
+и уменьшения разрешения на эту сумму.
+
+```solidity
+ /**
+ * @dev См. {IERC20-transferFrom}.
+ *
+ * Инициирует событие {Approval}, указывающее на обновленное разрешение. Это не
+ * требуется EIP. См. примечание в начале {ERC20}.
+ *
+ * Требования:
+ *
+ * - `sender` и `recipient` не могут быть нулевыми адресами.
+ * - у `sender` должен быть баланс не менее `amount`.
+ * - у вызывающего должно быть разрешение на токены ``sender`` не менее
+ * `amount`.
+ */
+ function transferFrom(address sender, address recipient, uint256 amount) public virtual
+ override returns (bool) {
+ _transfer(sender, recipient, amount);
+```
+
+
+
+Вызов функции `a.sub(b, "message")` выполняет две вещи. Во-первых, он вычисляет `a-b`, что является новым разрешением.
+Во-вторых, он проверяет, что этот результат не является отрицательным. Если он отрицательный, вызов отменяется с предоставленным сообщением. Обратите внимание, что когда вызов отменяется, любая обработка, выполненная ранее во время этого вызова, игнорируется, поэтому нам не нужно
+отменять `_transfer`.
+
+```solidity
+ _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount,
+ "ERC20: сумма перевода превышает разрешение"));
+ return true;
+ }
+```
+
+#### Дополнения безопасности OpenZeppelin {#openzeppelin-safety-additions}
+
+Опасно устанавливать ненулевое разрешение на другое ненулевое значение,
+потому что вы контролируете только порядок своих собственных транзакций, а не чьих-либо еще. Представьте, что у вас
+есть два пользователя: Алиса, которая наивна, и Билл, который нечестен. Алиса хочет получить какую-то услугу от
+Билла, которая, по ее мнению, стоит пять токенов, поэтому она дает Биллу разрешение на пять токенов.
+
+Затем что-то меняется, и цена Билла повышается до десяти токенов. Алиса, которая все еще хочет получить услугу,
+отправляет транзакцию, которая устанавливает разрешение Билла на десять. В тот момент, когда Билл видит эту новую транзакцию
+в пуле транзакций, он отправляет транзакцию, которая тратит пять токенов Алисы и имеет гораздо
+более высокую цену на Газ, чтобы она была добыта быстрее. Таким образом, Билл может сначала потратить пять токенов, а затем,
+когда новое разрешение Алисы будет добыто, потратить еще десять, что в общей сложности составит пятнадцать токенов — больше, чем
+Алиса намеревалась разрешить. Этот метод называется
+[опережением (фронтраннинг)](https://consensysdiligence.github.io/smart-contract-best-practices/attacks/#front-running)
+
+| Транзакция Алисы | Nonce Алисы | Транзакция Билла | Nonce Билла | Разрешение для Билла | Общий доход Билла от Алисы |
+| ------------------------------------ | ----------- | ------------------------------------------------ | ----------- | -------------------- | -------------------------- |
+| approve(Bill, 5) | 10 | | | 5 | 0 |
+| | | transferFrom(Alice, Bill, 5) | 10,123 | 0 | 5 |
+| approve(Bill, 10) | 11 | | | 10 | 5 |
+| | | transferFrom(Alice, Bill, 10) | 10,124 | 0 | 15 |
+
+Чтобы избежать этой проблемы, эти две функции (`increaseAllowance` и `decreaseAllowance`) позволяют вам
+изменять разрешение на определенную сумму. Так что, если Билл уже потратил пять токенов, он сможет
+потратить еще пять. В зависимости от времени это может работать двумя способами, оба из
+которых заканчиваются тем, что Билл получает только десять токенов:
+
+A:
+
+| Транзакция Алисы | Nonce Алисы | Транзакция Билла | Nonce Билла | Разрешение для Билла | Общий доход Билла от Алисы |
+| --------------------------------------------- | ----------: | ----------------------------------------------- | ----------: | -------------------: | -------------------------- |
+| approve(Bill, 5) | 10 | | | 5 | 0 |
+| | | transferFrom(Alice, Bill, 5) | 10,123 | 0 | 5 |
+| increaseAllowance(Bill, 5) | 11 | | | 0+5 = 5 | 5 |
+| | | transferFrom(Alice, Bill, 5) | 10,124 | 0 | 10 |
+
+B:
+
+| Транзакция Алисы | Nonce Алисы | Транзакция Билла | Nonce Билла | Разрешение для Билла | Общий доход Билла от Алисы |
+| --------------------------------------------- | ----------: | ------------------------------------------------ | ----------: | -------------------: | -------------------------: |
+| approve(Bill, 5) | 10 | | | 5 | 0 |
+| increaseAllowance(Bill, 5) | 11 | | | 5+5 = 10 | 0 |
+| | | transferFrom(Alice, Bill, 10) | 10,124 | 0 | 10 |
+
+```solidity
+ /**
+ * @dev Атомарно увеличивает разрешение, предоставленное `spender` вызывающей стороной.
+ *
+ * Это альтернатива {approve}, которая может использоваться для смягчения
+ * проблем, описанных в {IERC20-approve}.
+ *
+ * Инициирует событие {Approval}, указывающее на обновленное разрешение.
+ *
+ * Требования:
+ *
+ * - `spender` не может быть нулевым адресом.
+ */
+ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
+ _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
+ return true;
+ }
+```
+
+Функция `a.add(b)` — это безопасное сложение. В маловероятном случае, если `a`+`b`>=`2^256`, оно не переносится
+, как обычное сложение.
+
+```solidity
+
+ /**
+ * @dev Атомарно уменьшает разрешение, предоставленное `spender` вызывающей стороной.
+ *
+ * Это альтернатива {approve}, которая может использоваться для смягчения
+ * проблем, описанных в {IERC20-approve}.
+ *
+ * Инициирует событие {Approval}, указывающее на обновленное разрешение.
+ *
+ * Требования:
+ *
+ * - `spender` не может быть нулевым адресом.
+ * - `spender` должен иметь разрешение для вызывающей стороны не менее
+ * `subtractedValue`.
+ */
+ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
+ _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue,
+ "ERC20: уменьшенное разрешение ниже нуля"));
+ return true;
+ }
+```
+
+### Функции, изменяющие информацию о токене {#functions-that-modify-token-information}
+
+Это четыре функции, которые выполняют фактическую работу: `_transfer`, `_mint`, `_burn` и `_approve`.
+
+#### Функция _transfer {#_transfer}
+
+```solidity
+ /**
+ * @dev Перемещает токены в размере `amount` от `sender` к `recipient`.
+ *
+ * Эта внутренняя функция эквивалентна {transfer} и может использоваться для
+ * реализации, например, автоматических комиссий за токены, механизмов слэшинга и т. д.
+ *
+ * Инициирует событие {Transfer}.
+ *
+ * Требования:
+ *
+ * - `sender` не может быть нулевым адресом.
+ * - `recipient` не может быть нулевым адресом.
+ * - у `sender` должен быть баланс не менее `amount`.
+ */
+ function _transfer(address sender, address recipient, uint256 amount) internal virtual {
+```
+
+Эта функция, `_transfer`, переводит токены с одного аккаунта на другой. Она вызывается как
+`transfer` (для переводов с собственного счета отправителя), так и `transferFrom` (для использования разрешений
+для перевода со счета другого лица).
+
+
+
+```solidity
+ require(sender != address(0), "ERC20: перевод с нулевого адреса");
+ require(recipient != address(0), "ERC20: перевод на нулевой адрес");
+```
+
+Никто на самом деле не владеет нулевым адресом в Ethereum (то есть никто не знает приватный ключ, соответствующий публичный ключ которого
+преобразуется в нулевой адрес). Когда люди используют этот адрес, это обычно ошибка программного обеспечения, поэтому мы
+прерываем операцию, если нулевой адрес используется как отправитель или получатель.
+
+
+
+```solidity
+ _beforeTokenTransfer(sender, recipient, amount);
+
+```
+
+Есть два способа использовать этот контракт:
+
+1. Использовать его как шаблон для своего собственного кода
+2. [Наследоваться от него](https://www.bitdegree.org/learn/solidity-inheritance) и переопределять только те функции, которые вам нужно изменить
+
+Второй метод намного лучше, потому что код OpenZeppelin ERC-20 уже был проверен и признан безопасным. Когда вы используете наследование,
+ясно, какие функции вы изменяете, и чтобы доверять вашему контракту, людям нужно проверить только эти конкретные функции.
+
+Часто бывает полезно выполнять функцию каждый раз, когда токены переходят из рук в руки. Однако `_transfer` — очень важная функция, и ее можно
+написать небезопасно (см. ниже), поэтому лучше ее не переопределять. Решение — `_beforeTokenTransfer`,
+[функция-перехватчик (hook)](https://wikipedia.org/wiki/Hooking). Вы можете переопределить эту функцию, и она будет вызываться при каждом переводе.
+
+
+
+```solidity
+ _balances[sender] = _balances[sender].sub(amount, "ERC20: сумма перевода превышает баланс");
+ _balances[recipient] = _balances[recipient].add(amount);
+```
+
+Это строки, которые фактически выполняют перевод. Обратите внимание, что между ними **ничего** нет, и что мы вычитаем
+переведенную сумму у отправителя перед тем, как добавить ее получателю. Это важно, потому что если бы посредине был
+вызов другого контракта, его можно было бы использовать для обмана этого контракта. Таким образом, перевод
+является атомарным, ничего не может произойти в его середине.
+
+
+
+```solidity
+ emit Transfer(sender, recipient, amount);
+ }
+```
+
+Наконец, инициируйте событие `Transfer`. События недоступны для Умных контрактов, но код, работающий вне блокчейна,
+может прослушивать события и реагировать на них. Например, кошелек может отслеживать, когда владелец получает больше токенов.
+
+#### Функции _mint и _burn {#_mint-and-_burn}
+
+Эти две функции (`_mint` и `_burn`) изменяют общее предложение токенов.
+Они являются внутренними, и в этом контракте нет функции, которая их вызывает,
+поэтому они полезны только в том случае, если вы наследуете от контракта и добавляете свою собственную
+логику для решения, при каких условиях создавать новые токены или сжигать существующие.
+
+**ПРИМЕЧАНИЕ:** У каждого токена ERC-20 своя бизнес-логика, которая диктует управление токенами.
+Например, контракт с фиксированным предложением может вызывать `_mint` только
+в конструкторе и никогда не вызывать `_burn`. Контракт, который продает токены,
+будет вызывать `_mint` при получении оплаты и, предположительно, вызывать `_burn` в какой-то момент,
+чтобы избежать безудержной инфляции.
+
+```solidity
+ /** @dev Создает `amount` токенов и назначает их `account`, увеличивая
+ * общее предложение.
+ *
+ * Инициирует событие {Transfer} с `from`, установленным на нулевой адрес.
+ *
+ * Требования:
+ *
+ * - `to` не может быть нулевым адресом.
+ */
+ function _mint(address account, uint256 amount) internal virtual {
+ require(account != address(0), "ERC20: создание токенов на нулевой адрес");
+ _beforeTokenTransfer(address(0), account, amount);
+ _totalSupply = _totalSupply.add(amount);
+ _balances[account] = _balances[account].add(amount);
+ emit Transfer(address(0), account, amount);
+ }
+```
+
+Не забудьте обновить `_totalSupply` при изменении общего количества токенов.
+
+
+
+```solidity
+ /**
+ * @dev Уничтожает `amount` токенов с `account`, уменьшая
+ * общее предложение.
+ *
+ * Инициирует событие {Transfer} с `to`, установленным на нулевой адрес.
+ *
+ * Требования:
+ *
+ * - `account` не может быть нулевым адресом.
+ * - `account` должен иметь не менее `amount` токенов.
+ */
+ function _burn(address account, uint256 amount) internal virtual {
+ require(account != address(0), "ERC20: сжигание с нулевого адреса");
+
+ _beforeTokenTransfer(account, address(0), amount);
+
+ _balances[account] = _balances[account].sub(amount, "ERC20: сумма сжигания превышает баланс");
+ _totalSupply = _totalSupply.sub(amount);
+ emit Transfer(account, address(0), amount);
+ }
+```
+
+Функция `_burn` почти идентична `_mint`, за исключением того, что она работает в обратном направлении.
+
+#### Функция _approve {#_approve}
+
+Это функция, которая фактически определяет разрешения. Обратите внимание, что она позволяет владельцу указать
+разрешение, превышающее текущий баланс владельца. Это нормально, потому что баланс
+проверяется во время перевода, когда он может отличаться от баланса при
+создании разрешения.
+
+```solidity
+ /**
+ * @dev Устанавливает `amount` в качестве разрешения `spender` над токенами `owner`.
+ *
+ * Эта внутренняя функция эквивалентна `approve` и может использоваться для
+ * установки, например, автоматических разрешений для определенных подсистем и т. д.
+ *
+ * Инициирует событие {Approval}.
+ *
+ * Требования:
+ *
+ * - `owner` не может быть нулевым адресом.
+ * - `spender` не может быть нулевым адресом.
+ */
+ function _approve(address owner, address spender, uint256 amount) internal virtual {
+ require(owner != address(0), "ERC20: разрешение с нулевого адреса");
+ require(spender != address(0), "ERC20: разрешение на нулевой адрес");
+
+ _allowances[owner][spender] = amount;
+```
+
+
+
+Инициируйте событие `Approval`. В зависимости от того, как написано приложение, контракт тратящего может быть уведомлен о
+разрешении либо владельцем, либо сервером, который прослушивает эти события.
+
+```solidity
+ emit Approval(owner, spender, amount);
+ }
+
+```
+
+### Изменение переменной decimals {#modify-the-decimals-variable}
+
+```solidity
+
+
+ /**
+ * @dev Устанавливает {decimals} на значение, отличное от значения по умолчанию 18.
+ *
+ * ВНИМАНИЕ: Эту функцию следует вызывать только из конструктора. Большинство
+ * приложений, взаимодействующих с контрактами токенов, не ожидают,
+ * что {decimals} когда-либо изменится, и могут работать неправильно, если это произойдет.
+ */
+ function _setupDecimals(uint8 decimals_) internal {
+ _decimals = decimals_;
+ }
+```
+
+Эта функция изменяет переменную `_decimals`, которая используется для того, чтобы сообщить пользовательским интерфейсам, как интерпретировать сумму.
+Вы должны вызывать ее из конструктора. Было бы нечестно вызывать ее в любой последующий момент, и приложения
+не предназначены для обработки этого.
+
+### Перехватчики {#hooks}
+
+```solidity
+
+ /**
+ * @dev Перехватчик, который вызывается перед любым переводом токенов. Это включает
+ * создание и сжигание.
+ *
+ * Условия вызова:
+ *
+ * - когда `from` и `to` оба не равны нулю, `amount` токенов ``from``
+ * будет переведено `to`.
+ * - когда `from` равно нулю, `amount` токенов будет создано для `to`.
+ * - когда `to` равно нулю, `amount` токенов ``from`` будет сожжено.
+ * - `from` и `to` никогда не равны нулю одновременно.
+ *
+ * Чтобы узнать больше о перехватчиках, перейдите к xref:ROOT:extending-contracts.adoc#using-hooks[Использование перехватчиков].
+ */
+ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
+}
+```
+
+Это функция-перехватчик, которая вызывается во время переводов. Здесь она пуста, но если вам нужно,
+чтобы она что-то делала, вы просто переопределяете ее.
+
+## Заключение {#conclusion}
+
+Для обзора, вот некоторые из самых важных идей в этом контракте (по моему мнению, ваше может отличаться):
+
+- _В блокчейне не существует секретов_. Любая информация, к которой может получить доступ Умный контракт,
+ доступна всему миру.
+- Вы можете контролировать порядок своих собственных транзакций, но не то, когда происходят транзакции
+ других людей. Именно по этой причине изменение разрешения может быть опасным, потому что оно позволяет
+ тратящему потратить сумму обоих разрешений.
+- Значения типа `uint256` циклически переносятся. Другими словами, _0-1=2^256-1_. Если это нежелательное
+ поведение, вы должны проверять его (или использовать библиотеку SafeMath, которая делает это за вас). Обратите внимание, что это изменилось в
+ [Solidity 0.8.0](https://docs.soliditylang.org/en/breaking/080-breaking-changes.html).
+- Выполняйте все изменения состояния определенного типа в определенном месте, потому что это облегчает аудит.
+ Именно по этой причине у нас есть, например, `_approve`, который вызывается `approve`, `transferFrom`,
+ `increaseAllowance` и `decreaseAllowance`
+- Изменения состояния должны быть атомарными, без каких-либо других действий в их середине (как вы можете видеть
+ в `_transfer`). Это связано с тем, что во время изменения состояния у вас несогласованное состояние. Например,
+ между моментом вычета из баланса отправителя и моментом добавления к балансу
+ получателя существует меньше токенов, чем должно быть. Этим потенциально можно злоупотребить, если
+ между ними есть операции, особенно вызовы другого контракта.
+
+Теперь, когда вы увидели, как написан контракт OpenZeppelin ERC-20, и особенно как он
+сделан более безопасным, идите и пишите свои собственные безопасные контракты и приложения.
+
+[Больше моих работ смотрите здесь](https://cryptodocguy.pro/).
diff --git a/public/content/translations/ru/developers/tutorials/erc20-with-safety-rails/index.md b/public/content/translations/ru/developers/tutorials/erc20-with-safety-rails/index.md
new file mode 100644
index 00000000000..318602e9753
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/erc20-with-safety-rails/index.md
@@ -0,0 +1,217 @@
+---
+title: "ERC-20 с мерами предосторожности"
+description: "Как помочь людям избежать глупых ошибок"
+author: Ori Pomerantz
+lang: ru
+tags: [ "erc-20" ]
+skill: beginner
+published: 2022-08-15
+---
+
+## Введение {#introduction}
+
+Одно из замечательных качеств Ethereum — это отсутствие центрального органа, который мог бы изменять или отменять ваши транзакции. Одна из больших проблем Ethereum заключается в том, что нет центрального органа, уполномоченного исправлять ошибки пользователей или отменять незаконные транзакции. В этой статье вы узнаете о некоторых распространенных ошибках, которые пользователи совершают с токенами [ERC-20](/developers/docs/standards/tokens/erc-20/), а также о том, как создавать контракты ERC-20, которые помогают пользователям избежать этих ошибок или которые предоставляют центральному органу некоторые полномочия (например, для замораживания аккаунтов).
+
+Обратите внимание, что, хотя мы будем использовать [контракт токена ERC-20 от OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20), в этой статье он не объясняется в мельчайших подробностях. Эту информацию можно найти [здесь](/developers/tutorials/erc20-annotated-code).
+
+Если вы хотите увидеть полный исходный код:
+
+1. Откройте [Remix IDE](https://remix.ethereum.org/).
+2. Нажмите на значок клонирования github ().
+3. Клонируйте репозиторий github `https://github.com/qbzzt/20220815-erc20-safety-rails`.
+4. Откройте **contracts > erc20-safety-rails.sol**.
+
+## Создание контракта ERC-20 {#creating-an-erc-20-contract}
+
+Прежде чем мы сможем добавить функциональность мер предосторожности, нам понадобится контракт ERC-20. В этой статье мы воспользуемся [Мастером контрактов OpenZeppelin](https://docs.openzeppelin.com/contracts/5.x/wizard). Откройте его в другом браузере и следуйте этим инструкциям:
+
+1. Выберите **ERC20**.
+
+2. Введите следующие настройки:
+
+ | Параметр | Значение |
+ | ---------------------- | ---------------- |
+ | Имя | SafetyRailsToken |
+ | Символ | SAFE |
+ | Premint | 1000 |
+ | Функции | Нет |
+ | Контроль доступа | Ownable |
+ | Возможность обновления | Нет |
+
+3. Прокрутите вверх и нажмите **Открыть в Remix** (для Remix) или **Загрузить**, чтобы использовать другую среду. Я буду исходить из того, что вы используете Remix. Если вы используете что-то другое, просто внесите соответствующие изменения.
+
+4. Теперь у нас есть полнофункциональный контракт ERC-20. Вы можете развернуть `.deps` > `npm`, чтобы увидеть импортированный код.
+
+5. Скомпилируйте, разверните и поработайте с контрактом, чтобы убедиться, что он функционирует как контракт ERC-20. Если вам нужно научиться пользоваться Remix, [воспользуйтесь этим руководством](https://remix.ethereum.org/?#activate=udapp,solidity,LearnEth).
+
+## Распространенные ошибки {#common-mistakes}
+
+### Ошибки {#the-mistakes}
+
+Иногда пользователи отправляют токены на неверный адрес. Хотя мы не можем читать их мысли, чтобы знать, что они хотели сделать, есть два типа ошибок, которые случаются часто и легко обнаруживаются:
+
+1. Отправка токенов на собственный адрес контракта. Например, [токену OP от Optimism](https://optimism.mirror.xyz/qvd0WfuLKnePm1Gxb9dpGchPf5uDz5NSMEFdgirDS4c) удалось накопить [более 120 000](https://optimism.blockscout.com/address/0x4200000000000000000000000000000000000042) токенов OP менее чем за два месяца. Это представляет собой значительное состояние, которое, по-видимому, люди просто потеряли.
+
+2. Отправка токенов на пустой адрес, который не соответствует [внешнему аккаунту](/developers/docs/accounts/#externally-owned-accounts-and-key-pairs) или [умному контракту](/developers/docs/smart-contracts). Хотя у меня нет статистики о том, как часто это происходит, [один инцидент мог стоить 20 000 000 токенов](https://gov.optimism.io/t/message-to-optimism-community-from-wintermute/2595).
+
+### Предотвращение переводов {#preventing-transfers}
+
+Контракт ERC-20 от OpenZeppelin включает [перехватчик (hook) `_beforeTokenTransfer`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol#L364-L368), который вызывается перед передачей токена. По умолчанию этот перехватчик ничего не делает, но мы можем «повесить» на него собственную функциональность, например, проверки, которые отменяют транзакцию в случае возникновения проблемы.
+
+Чтобы использовать перехватчик, добавьте эту функцию после конструктора:
+
+```solidity
+ function _beforeTokenTransfer(address from, address to, uint256 amount)
+ internal virtual
+ override(ERC20)
+ {
+ super._beforeTokenTransfer(from, to, amount);
+ }
+```
+
+Некоторые части этой функции могут быть новыми, если вы не очень хорошо знакомы с Solidity:
+
+```solidity
+ internal virtual
+```
+
+Ключевое слово `virtual` означает, что так же, как мы унаследовали функциональность от `ERC20` и переопределили эту функцию, другие контракты могут наследоваться от нас и переопределять эту функцию.
+
+```solidity
+ override(ERC20)
+```
+
+Мы должны явно указать, что мы [переопределяем](https://docs.soliditylang.org/en/v0.8.15/contracts.html#function-overriding) определение `_beforeTokenTransfer` для токена ERC20. В целом, с точки зрения безопасности, явные определения намного лучше, чем неявные — вы не можете забыть, что что-то сделали, если это прямо перед вами. Это также причина, по которой нам нужно указать, чей `_beforeTokenTransfer` суперкласса мы переопределяем.
+
+```solidity
+ super._beforeTokenTransfer(from, to, amount);
+```
+
+Эта строка вызывает функцию `_beforeTokenTransfer` контракта или контрактов, от которых мы унаследовали и в которых она есть. В данном случае это только `ERC20`, у `Ownable` этого перехватчика нет. Несмотря на то, что в настоящее время `ERC20._beforeTokenTransfer` ничего не делает, мы вызываем его на случай, если в будущем будет добавлена функциональность (и мы затем решим переразвернуть контракт, потому что контракты не меняются после развертывания).
+
+### Программирование требований {#coding-the-requirements}
+
+Мы хотим добавить в функцию следующие требования:
+
+- Адрес `to` не может быть равен `address(this)`, то есть адресу самого контракта ERC-20.
+- Адрес `to` не может быть пустым, он должен быть либо:
+ - Внешний аккаунт (EOA). Мы не можем напрямую проверить, является ли адрес EOA, но мы можем проверить баланс ETH по этому адресу. EOA почти всегда имеют баланс, даже если они больше не используются — трудно очистить их до последнего wei.
+ - Умный контракт. Проверить, является ли адрес умным контрактом, немного сложнее. Существует код операции (opcode), который проверяет длину внешнего кода, он называется [`EXTCODESIZE`](https://www.evm.codes/#3b), но он недоступен напрямую в Solidity. Для этого мы должны использовать [Yul](https://docs.soliditylang.org/en/v0.8.15/yul.html), который является ассемблером EVM. Есть и другие значения, которые мы могли бы использовать из Solidity ([`.code` и `.codehash`](https://docs.soliditylang.org/en/v0.8.15/units-and-global-variables.html#members-of-address-types)), но они стоят дороже.
+
+Давайте рассмотрим новый код построчно:
+
+```solidity
+ require(to != address(this), "Нельзя отправлять токены на адрес контракта");
+```
+
+Это первое требование, проверка того, что `to` и `this(address)` — это не одно и то же.
+
+```solidity
+ bool isToContract;
+ assembly {
+ isToContract := gt(extcodesize(to), 0)
+ }
+```
+
+Вот как мы проверяем, является ли адрес контрактом. Мы не можем получать выходные данные напрямую из Yul, поэтому вместо этого мы определяем переменную для хранения результата (в данном случае `isToContract`). Yul работает так, что каждый код операции (opcode) считается функцией. Итак, сначала мы вызываем [`EXTCODESIZE`](https://www.evm.codes/#3b), чтобы получить размер контракта, а затем используем [`GT`](https://www.evm.codes/#11), чтобы проверить, что он не равен нулю (мы имеем дело с беззнаковыми целыми числами, поэтому, конечно, он не может быть отрицательным). Затем мы записываем результат в `isToContract`.
+
+```solidity
+ require(to.balance != 0 || isToContract, "Нельзя отправлять токены на пустой адрес");
+```
+
+И, наконец, у нас есть фактическая проверка на пустые адреса.
+
+## Административный доступ {#admin-access}
+
+Иногда полезно иметь администратора, который может исправлять ошибки. Чтобы уменьшить вероятность злоупотреблений, этот администратор может быть [мультиподписным кошельком (multisig)](https://blog.logrocket.com/security-choices-multi-signature-wallets/), чтобы для выполнения действия требовалось согласие нескольких человек. В этой статье мы рассмотрим две административные функции:
+
+1. Замораживание и размораживание аккаунтов. Это может быть полезно, например, когда аккаунт может быть скомпрометирован.
+2. Очистка активов.
+
+ Иногда мошенники отправляют поддельные токены на контракт настоящего токена, чтобы завоевать доверие. Например, [смотрите здесь](https://optimism.blockscout.com/token/0x2348B1a1228DDCd2dB668c3d30207c3E1852fBbe?tab=holders). Настоящий контракт ERC-20 — это [0x4200....0042](https://optimism.blockscout.com/token/0x4200000000000000000000000000000000000042). Мошеннический контракт, который выдает себя за него, — это [0x234....bbe](https://optimism.blockscout.com/token/0x2348B1a1228DDCd2dB668c3d30207c3E1852fBbe).
+
+ Также возможно, что люди по ошибке отправят настоящие токены ERC-20 на наш контракт, и это еще одна причина, по которой стоит иметь способ их оттуда вывести.
+
+OpenZeppelin предоставляет два механизма для обеспечения административного доступа:
+
+- Контракты [`Ownable`](https://docs.openzeppelin.com/contracts/5.x/access-control#ownership-and-ownable) имеют одного владельца. Функции, имеющие [модификатор](https://www.tutorialspoint.com/solidity/solidity_function_modifiers.htm) `onlyOwner`, могут вызываться только этим владельцем. Владельцы могут передать право собственности кому-то другому или полностью от него отказаться. Права всех остальных аккаунтов обычно идентичны.
+- Контракты [`AccessControl`](https://docs.openzeppelin.com/contracts/5.x/access-control#role-based-access-control) имеют [управление доступом на основе ролей (RBAC)](https://en.wikipedia.org/wiki/Role-based_access_control).
+
+Для простоты в этой статье мы используем `Ownable`.
+
+### Замораживание и размораживание счетов {#freezing-and-thawing-contracts}
+
+Для замораживания и размораживания счетов требуется несколько изменений:
+
+- [Сопоставление (mapping)](https://www.tutorialspoint.com/solidity/solidity_mappings.htm) адресов с [логическими значениями (booleans)](https://en.wikipedia.org/wiki/Boolean_data_type) для отслеживания замороженных адресов. Все значения изначально равны нулю, что для логических значений интерпретируется как false. Это то, что нам нужно, потому что по умолчанию аккаунты не заморожены.
+
+ ```solidity
+ mapping(address => bool) public frozenAccounts;
+ ```
+
+- [События (events)](https://www.tutorialspoint.com/solidity/solidity_events.htm), информирующие всех заинтересованных лиц о замораживании или размораживании аккаунта. Технически говоря, события не требуются для этих действий, но они помогают коду вне блокчейна (offchain) прослушивать эти события и знать, что происходит. Считается хорошим тоном, когда умный контракт генерирует их, когда происходит что-то, что может быть важно для кого-то еще.
+
+ События индексируются, поэтому можно будет найти все случаи замораживания или размораживания аккаунта.
+
+ ```solidity
+ // Когда аккаунты замораживаются или размораживаются
+ event AccountFrozen(address indexed _addr);
+ event AccountThawed(address indexed _addr);
+ ```
+
+- Функции для замораживания и размораживания аккаунтов. Эти две функции почти идентичны, поэтому мы рассмотрим только функцию заморозки.
+
+ ```solidity
+ function freezeAccount(address addr)
+ public
+ onlyOwner
+ ```
+
+ Функции, помеченные как [`public`](https://www.tutorialspoint.com/solidity/solidity_contracts.htm), могут вызываться из других умных контрактов или напрямую через транзакцию.
+
+ ```solidity
+ {
+ require(!frozenAccounts[addr], "Аккаунт уже заморожен");
+ frozenAccounts[addr] = true;
+ emit AccountFrozen(addr);
+ } // freezeAccount
+ ```
+
+ Если аккаунт уже заморожен, отменить транзакцию. В противном случае заморозьте его и сгенерируйте (`emit`) событие.
+
+- Измените `_beforeTokenTransfer`, чтобы предотвратить перемещение денег с замороженного аккаунта. Обратите внимание, что деньги все еще можно переводить на замороженный аккаунт.
+
+ ```solidity
+ require(!frozenAccounts[from], "Аккаунт заморожен");
+ ```
+
+### Очистка активов {#asset-cleanup}
+
+Чтобы высвободить токены ERC-20, хранящиеся в этом контракте, нам нужно вызвать функцию в контракте токена, которому они принадлежат, — либо [`transfer`](https://eips.ethereum.org/EIPS/eip-20#transfer), либо [`approve`](https://eips.ethereum.org/EIPS/eip-20#approve). В данном случае нет смысла тратить газ на разрешения (allowances), мы можем просто перевести их напрямую.
+
+```solidity
+ function cleanupERC20(
+ address erc20,
+ address dest
+ )
+ public
+ onlyOwner
+ {
+ IERC20 token = IERC20(erc20);
+```
+
+Это синтаксис для создания объекта для контракта, когда мы получаем адрес. Мы можем это сделать, потому что у нас есть определение для токенов ERC20 как часть исходного кода (см. строку 4), и этот файл включает [определение для IERC20](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol), интерфейс для контракта ERC-20 от OpenZeppelin.
+
+```solidity
+ uint balance = token.balanceOf(address(this));
+ token.transfer(dest, balance);
+ }
+```
+
+Это функция очистки, поэтому, предположительно, мы не хотим оставлять никаких токенов. Вместо того чтобы получать баланс от пользователя вручную, мы можем автоматизировать этот процесс.
+
+## Заключение {#conclusion}
+
+Это не идеальное решение — идеального решения для проблемы «пользователь совершил ошибку» не существует. Однако использование подобных проверок может по крайней мере предотвратить некоторые ошибки. Возможность замораживать аккаунты, хотя и является опасной, может быть использована для ограничения ущерба от некоторых взломов, лишая хакера украденных средств.
+
+[Больше моих работ смотрите здесь](https://cryptodocguy.pro/).
diff --git a/public/content/translations/ru/developers/tutorials/ethereum-for-web2-auth/index.md b/public/content/translations/ru/developers/tutorials/ethereum-for-web2-auth/index.md
new file mode 100644
index 00000000000..09a452f55ca
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/ethereum-for-web2-auth/index.md
@@ -0,0 +1,886 @@
+---
+title: "Использование Ethereum для аутентификации web2"
+description: "После прочтения этого руководства разработчик сможет интегрировать вход в систему Ethereum (web3) с входом в систему SAML — стандартом, используемым в web2 для обеспечения единого входа и других сопутствующих услуг. Это позволяет аутентифицировать доступ к ресурсам web2 с помощью подписей Ethereum, при этом атрибуты пользователя берутся из аттестаций."
+author: Ori Pomerantz
+tags: [ "web2", "аутентификация", "eas" ]
+skill: beginner
+lang: ru
+published: 2025-04-30
+---
+
+## Введение
+
+[SAML](https://www.onelogin.com/learn/saml) — это стандарт, используемый в web2, который позволяет [поставщику удостоверений (IdP)](https://en.wikipedia.org/wiki/Identity_provider#SAML_identity_provider) предоставлять информацию о пользователе [поставщикам услуг (SP)](https://en.wikipedia.org/wiki/Service_provider_\(SAML\)).
+
+В этом руководстве вы узнаете, как интегрировать подписи Ethereum с SAML, чтобы позволить пользователям использовать свои кошельки Ethereum для аутентификации в сервисах web2, которые пока не поддерживают Ethereum нативно.
+
+Обратите внимание, что это руководство предназначено для двух разных аудиторий:
+
+- Пользователи Ethereum, которые разбираются в Ethereum и хотят изучить SAML
+- Пользователи Web2, которые разбираются в SAML и аутентификации web2 и хотят изучить Ethereum
+
+В результате в нем будет много вводного материала, который вы уже знаете. Не стесняйтесь пропускать его.
+
+### SAML для пользователей Ethereum
+
+SAML — это централизованный протокол. Поставщик услуг (SP) принимает утверждения (например, "это мой пользователь John, у него должны быть разрешения на выполнение A, B и C") от поставщика удостоверений (IdP), только если у него есть предварительно установленные доверительные отношения либо с ним, либо с [центром сертификации](https://www.ssl.com/article/what-is-a-certificate-authority-ca/), который подписал сертификат этого IdP.
+
+Например, SP может быть туристическим агентством, предоставляющим туристические услуги компаниям, а IdP — внутренним веб-сайтом компании. Когда сотрудникам необходимо забронировать деловую поездку, туристическое агентство отправляет их на аутентификацию в компанию, прежде чем позволить им фактически забронировать поездку.
+
+
+
+Таким образом три сущности — браузер, SP и IdP — договариваются о доступе. SP не нужно заранее ничего знать о пользователе, использующем браузер, ему достаточно доверять IdP.
+
+### Ethereum для пользователей SAML
+
+Ethereum — это децентрализованная система.
+
+
+
+У пользователей есть приватный ключ (обычно хранящийся в расширении браузера). Из приватного ключа можно получить публичный ключ, а из него — 20-байтовый адрес. Когда пользователям необходимо войти в систему, им предлагается подписать сообщение с помощью nonce (одноразового значения). Сервер может проверить, что подпись была создана этим адресом.
+
+
+
+Подпись подтверждает только адрес Ethereum. Чтобы получить другие атрибуты пользователя, обычно используются [аттестации](https://attest.org/). Аттестация обычно содержит следующие поля:
+
+- **Аттестующий**, адрес, который сделал аттестацию
+- **Получатель**, адрес, к которому применяется аттестация
+- **Данные**, аттестуемые данные, такие как имя, разрешения и т. д.
+- **Схема**, идентификатор схемы, используемой для интерпретации данных.
+
+Из-за децентрализованной природы Ethereum любой пользователь может делать аттестации. Личность аттестующего важна для определения того, какие аттестации мы считаем надежными.
+
+## Настройка
+
+Первый шаг — обеспечить взаимодействие между SAML SP и SAML IdP.
+
+1. Загрузите программное обеспечение. Пример программного обеспечения для этой статьи находится [на GitHub](https://github.com/qbzzt/250420-saml-ethereum). Разные этапы хранятся в разных ветках, для этого этапа вам нужна ветка `saml-only`
+
+ ```sh
+ git clone https://github.com/qbzzt/250420-saml-ethereum -b saml-only
+ cd 250420-saml-ethereum
+ pnpm install
+ ```
+
+2. Создайте ключи с самоподписанными сертификатами. Это означает, что ключ является собственным центром сертификации и должен быть импортирован вручную поставщику услуг. Дополнительную информацию см. в [документации OpenSSL](https://docs.openssl.org/master/man1/openssl-req/).
+
+ ```sh
+ mkdir keys
+ cd keys
+ openssl req -new -x509 -days 365 -nodes -sha256 -out saml-sp.crt -keyout saml-sp.pem -subj /CN=sp/
+ openssl req -new -x509 -days 365 -nodes -sha256 -out saml-idp.crt -keyout saml-idp.pem -subj /CN=idp/
+ cd ..
+ ```
+
+3. Запустите серверы (и SP, и IdP)
+
+ ```sh
+ pnpm start
+ ```
+
+4. Перейдите по URL-адресу SP [http://localhost:3000/](http://localhost:3000/) и нажмите кнопку для перенаправления на IdP (порт 3001).
+
+5. Укажите свой адрес электронной почты в IdP и нажмите **Login to the service provider**. Убедитесь, что вы были перенаправлены обратно к поставщику услуг (порт 3000) и что он распознает вас по вашему адресу электронной почты.
+
+### Подробное объяснение
+
+Вот что происходит, шаг за шагом:
+
+
+
+#### src/config.mts
+
+Этот файл содержит конфигурацию как для поставщика удостоверений, так и для поставщика услуг. Обычно это две разные сущности, но здесь для простоты мы можем использовать общий код.
+
+```typescript
+const fs = await import("fs")
+
+const protocol="http"
+```
+
+Пока мы просто тестируем, поэтому можно использовать HTTP.
+
+```typescript
+export const spCert = fs.readFileSync("keys/saml-sp.crt").toString()
+export const idpCert = fs.readFileSync("keys/saml-idp.crt").toString()
+```
+
+Прочтите публичные ключи, которые обычно доступны обоим компонентам (и либо доверяются напрямую, либо подписаны доверенным центром сертификации).
+
+```typescript
+export const spPort = 3000
+export const spHostname = "localhost"
+export const spDir = "sp"
+
+export const idpPort = 3001
+export const idpHostname = "localhost"
+export const idpDir = "idp"
+
+export const spUrl = `${protocol}://${spHostname}:${spPort}/${spDir}`
+export const idpUrl = `${protocol}://${idpHostname}:${idpPort}/${idpDir}`
+```
+
+URL-адреса для обоих компонентов.
+
+```typescript
+export const spPublicData = {
+```
+
+Публичные данные для поставщика услуг.
+
+```typescript
+ entityID: `${spUrl}/metadata`,
+```
+
+По соглашению, в SAML `entityID` — это URL-адрес, по которому доступны метаданные сущности. Эти метаданные соответствуют здешним публичным данным, за исключением того, что они представлены в формате XML.
+
+```typescript
+ wantAssertionsSigned: true,
+ authnRequestsSigned: false,
+ signingCert: spCert,
+ allowCreate: true,
+ assertionConsumerService: [{
+ Binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
+ Location: `${spUrl}/assertion`,
+ }]
+ }
+```
+
+Самым важным определением для наших целей является `assertionConsumerServer`. Это означает, что для утверждения чего-либо (например, "пользователь, который отправляет вам эту информацию, — somebody@example.com") поставщику услуг нам нужно использовать [HTTP POST](https://www.w3schools.com/tags/ref_httpmethods.asp) на URL-адрес `http://localhost:3000/sp/assertion`.
+
+```typescript
+export const idpPublicData = {
+ entityID: `${idpUrl}/metadata`,
+ signingCert: idpCert,
+ wantAuthnRequestsSigned: false,
+ singleSignOnService: [{
+ Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
+ Location: `${idpUrl}/login`
+ }],
+ singleLogoutService: [{
+ Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
+ Location: `${idpUrl}/logout`
+ }],
+ }
+```
+
+Публичные данные для поставщика удостоверений похожи. В нем указано, что для входа пользователя в систему необходимо отправить POST-запрос на `http://localhost:3001/idp/login`, а для выхода — на `http://localhost:3001/idp/logout`.
+
+#### src/sp.mts
+
+Это код, который реализует поставщика услуг.
+
+```typescript
+import * as config from "./config.mts"
+const fs = await import("fs")
+const saml = await import("samlify")
+```
+
+Мы используем библиотеку [`samlify`](https://www.npmjs.com/package/samlify) для реализации SAML.
+
+```typescript
+import * as validator from "@authenio/samlify-node-xmllint"
+saml.setSchemaValidator(validator)
+```
+
+Библиотека `samlify` ожидает наличия пакета для проверки того, что XML-документ корректен, подписан ожидаемым публичным ключом и т. д. Для этой цели мы используем [`@authenio/samlify-node-xmllint`](https://www.npmjs.com/package/@authenio/samlify-node-xmllint).
+
+```typescript
+const express = (await import("express")).default
+const spRouter = express.Router()
+const app = express()
+```
+
+[`Router`](https://expressjs.com/en/5x/api.html#router) из [`express`](https://expressjs.com/) — это "мини-веб-сайт", который можно встроить в другой веб-сайт. В данном случае мы используем его для группировки всех определений поставщика услуг.
+
+```typescript
+const spPrivateKey = fs.readFileSync("keys/saml-sp.pem").toString()
+
+const sp = saml.ServiceProvider({
+ privateKey: spPrivateKey,
+ ...config.spPublicData
+})
+```
+
+Собственное представление поставщика услуг о себе — это все публичные данные и приватный ключ, который он использует для подписи информации.
+
+```typescript
+const idp = saml.IdentityProvider(config.idpPublicData);
+```
+
+Публичные данные содержат все, что поставщик услуг должен знать о поставщике удостоверений.
+
+```typescript
+spRouter.get(`/metadata`,
+ (req, res) => res.header("Content-Type", "text/xml").send(sp.getMetadata())
+)
+```
+
+Чтобы обеспечить совместимость с другими компонентами SAML, поставщики услуг и удостоверений должны предоставлять свои публичные данные (называемые метаданными) в формате XML по адресу `/metadata`.
+
+```typescript
+spRouter.post(`/assertion`,
+```
+
+Это страница, к которой обращается браузер для самоидентификации. Утверждение включает идентификатор пользователя (здесь мы используем адрес электронной почты) и может содержать дополнительные атрибуты. Это обработчик для шага 7 на диаграмме последовательности выше.
+
+```typescript
+ async (req, res) => {
+ // console.log(`SAML response:\n${Buffer.from(req.body.SAMLResponse, 'base64').toString('utf-8')}`)
+```
+
+Вы можете использовать закомментированную команду, чтобы увидеть данные XML, предоставленные в утверждении. Они [закодированы в base64](https://en.wikipedia.org/wiki/Base64).
+
+```typescript
+ try {
+ const loginResponse = await sp.parseLoginResponse(idp, 'post', req);"
+```
+
+Проанализируйте запрос на вход от сервера удостоверений.
+
+```typescript
+ res.send(`
+
+
+
Hello ${loginResponse.extract.nameID}
+
+
+ `)
+ res.send();
+```
+
+Отправьте HTML-ответ, чтобы показать пользователю, что мы получили запрос на вход.
+
+```typescript
+ } catch (err) {
+ console.error('Error processing SAML response:', err);
+ res.status(400).send('SAML authentication failed');
+ }
+ }
+)
+```
+
+Сообщите пользователю в случае сбоя.
+
+```typescript
+spRouter.get('/login',
+```
+
+Создайте запрос на вход, когда браузер пытается получить эту страницу. Это обработчик для шага 1 на диаграмме последовательности выше.
+
+```typescript
+ async (req, res) => {
+ const loginRequest = await sp.createLoginRequest(idp, "post")
+```
+
+Получите информацию для отправки запроса на вход.
+
+```typescript
+ res.send(`
+
+
+
+```
+
+Эта страница автоматически отправляет форму (см. ниже). Таким образом, пользователю не нужно ничего делать для перенаправления. Это шаг 2 на диаграмме последовательности выше.
+
+```typescript
+
+
+
+ `)
+ }
+)
+
+app.use(express.urlencoded({extended: true}))
+```
+
+[Это промежуточное ПО](https://expressjs.com/en/5x/api.html#express.urlencoded) читает тело [HTTP-запроса](https://www.tutorialspoint.com/http/http_requests.htm). По умолчанию express игнорирует его, потому что большинство запросов этого не требуют. Нам это нужно, потому что POST использует тело запроса.
+
+```typescript
+app.use(`/${config.spDir}`, spRouter)
+```
+
+Подключите маршрутизатор в каталоге поставщика услуг (`/sp`).
+
+```typescript
+app.get("/", (req, res) => {
+ res.send(`
+
+
+
+
+
+ `)
+})
+```
+
+Если браузер пытается получить корневой каталог, предоставьте ему ссылку на страницу входа.
+
+```typescript
+app.listen(config.spPort, () => {
+ console.log(`service provider is running on http://${config.spHostname}:${config.spPort}`)
+})
+```
+
+Прослушивайте `spPort` с помощью этого приложения express.
+
+#### src/idp.mts
+
+Это поставщик удостоверений. Он очень похож на поставщика услуг, приведенные ниже объяснения относятся к тем частям, которые отличаются.
+
+```typescript
+const xmlParser = new (await import("fast-xml-parser")).XMLParser(
+ {
+ ignoreAttributes: false, // Сохранять атрибуты
+ attributeNamePrefix: "@_", // Префикс для атрибутов
+ }
+)
+```
+
+Нам нужно прочитать и понять XML-запрос, который мы получаем от поставщика услуг.
+
+```typescript
+const getLoginPage = requestId => `
+```
+
+Эта функция создает страницу с автоматически отправляемой формой, которая возвращается на шаге 4 диаграммы последовательности выше.
+
+```typescript
+
+
+ Страница входа
+
+
+
Страница входа
+
+
+
+
+const idpRouter = express.Router()
+
+idpRouter.post("/loginSubmitted", async (req, res) => {
+ const loginResponse = await idp.createLoginResponse(
+```
+
+Это обработчик для шага 5 на диаграмме последовательности выше. [`idp.createLoginResponse`](https://github.com/tngan/samlify/blob/master/src/entity-idp.ts#L73-L125) создает ответ на запрос входа.
+
+```typescript
+ sp,
+ {
+ authnContextClassRef: 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',
+ audience: sp.entityID,
+```
+
+Аудитория — это поставщик услуг.
+
+```typescript
+ extract: {
+ request: {
+ id: req.body.requestId
+ }
+ },
+```
+
+Информация, извлеченная из запроса. Единственный параметр, который нас интересует в запросе, — это requestId, который позволяет поставщику услуг сопоставлять запросы и ответы на них.
+
+```typescript
+ signingKey: { privateKey: idpPrivateKey, publicKey: config.idpCert } // Обеспечить подпись
+```
+
+Нам нужен `signingKey` для получения данных для подписи ответа. Поставщик услуг не доверяет неподписанным запросам.
+
+```typescript
+ },
+ "post",
+ {
+ email: req.body.email
+```
+
+Это поле с информацией о пользователе, которую мы отправляем обратно поставщику услуг.
+
+```typescript
+ }
+ );
+
+ res.send(`
+
+
+
+
+
+
+
+ `)
+})
+```
+
+Опять же, используйте автоматически отправляемую форму. Это шаг 6 на диаграмме последовательности выше.
+
+```typescript
+
+// Конечная точка IdP для запросов на вход
+idpRouter.post(`/login`,
+```
+
+Это конечная точка, которая получает запрос на вход от поставщика услуг. Это обработчик для шага 3 на диаграмме последовательности выше.
+
+```typescript
+ async (req, res) => {
+ try {
+ // Обходной путь, потому что мне не удалось заставить parseLoginRequest работать.
+ // const loginRequest = await idp.parseLoginRequest(sp, 'post', req)
+ const samlRequest = xmlParser.parse(Buffer.from(req.body.SAMLRequest, 'base64').toString('utf-8'))
+ res.send(getLoginPage(samlRequest["samlp:AuthnRequest"]["@_ID"]))
+```
+
+Мы должны иметь возможность использовать [`idp.parseLoginRequest`](https://github.com/tngan/samlify/blob/master/src/entity-idp.ts#L127-L144) для чтения ID запроса на аутентификацию. Однако мне не удалось заставить его работать, и не стоило тратить на это много времени, поэтому я просто использую [XML-парсер общего назначения](https://www.npmjs.com/package/fast-xml-parser). Информация, которая нам нужна, — это атрибут `ID` внутри тега ``, который находится на верхнем уровне XML.
+
+## Использование подписей Ethereum
+
+Теперь, когда мы можем отправлять идентификатор пользователя поставщику услуг, следующий шаг — получить идентификатор пользователя надежным способом. Viem позволяет нам просто запросить у кошелька адрес пользователя, но это означает запрос информации у браузера. Мы не контролируем браузер, поэтому не можем автоматически доверять ответу, который мы от него получаем.
+
+Вместо этого IdP отправит браузеру строку для подписи. Если кошелек в браузере подписывает эту строку, это означает, что это действительно тот самый адрес (то есть он знает приватный ключ, соответствующий этому адресу).
+
+Чтобы увидеть это в действии, остановите существующие IdP и SP и выполните следующие команды:
+
+```sh
+git checkout eth-signatures
+pnpm install
+pnpm start
+```
+
+Затем перейдите [к SP](http://localhost:3000) и следуйте инструкциям.
+
+Обратите внимание, что на данном этапе мы не знаем, как получить адрес электронной почты из адреса Ethereum, поэтому вместо этого мы сообщаем SP `<адрес Ethereum>@bad.email.address`.
+
+### Подробное объяснение
+
+Изменения касаются шагов 4-5 на предыдущей диаграмме.
+
+
+
+Единственный файл, который мы изменили, — `idp.mts`. Вот измененные части.
+
+```typescript
+import { v4 as uuidv4 } from 'uuid'
+import { verifyMessage } from 'viem'
+```
+
+Нам нужны эти две дополнительные библиотеки. Мы используем [`uuid`](https://www.npmjs.com/package/uuid) для создания значения [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce). Само значение не имеет значения, важен лишь тот факт, что оно используется только один раз.
+
+Библиотека [`viem`](https://viem.sh/) позволяет нам использовать определения Ethereum. Здесь она нужна нам для проверки действительности подписи.
+
+```typescript
+const loginPrompt = "Чтобы получить доступ к поставщику услуг, подпишите этот nonce: "
+```
+
+Кошелек запрашивает у пользователя разрешение на подписание сообщения. Сообщение, которое является просто nonce, может сбить с толку пользователей, поэтому мы включаем это приглашение.
+
+```typescript
+// Храните здесь requestID
+let nonces = {}
+```
+
+Нам нужна информация о запросе, чтобы иметь возможность ответить на него. Мы могли бы отправить ее с запросом (шаг 4) и получить обратно (шаг 5). Однако мы не можем доверять информации, которую получаем от браузера, находящегося под контролем потенциально враждебного пользователя. Поэтому лучше хранить ее здесь, используя nonce в качестве ключа.
+
+Обратите внимание, что для простоты мы делаем это здесь в виде переменной. Однако у этого есть несколько недостатков:
+
+- Мы уязвимы для атаки типа «отказ в обслуживании». Злонамеренный пользователь может попытаться войти в систему несколько раз, заполнив нашу память.
+- Если процесс IdP необходимо перезапустить, мы потеряем существующие значения.
+- Мы не можем распределять нагрузку между несколькими процессами, потому что у каждого будет своя переменная.
+
+В производственной системе мы бы использовали базу данных и реализовали какой-либо механизм истечения срока действия.
+
+```typescript
+const getSignaturePage = requestId => {
+ const nonce = uuidv4()
+ nonces[nonce] = requestId
+```
+
+Создайте nonce и сохраните `requestId` для будущего использования.
+
+```typescript
+ return `
+
+
+
+
+
+
Пожалуйста, подпишите
+
+
+
+
+
+`
+}
+```
+
+Остальное — просто стандартный HTML.
+
+```typescript
+idpRouter.get("/signature/:nonce/:account/:signature", async (req, res) => {
+```
+
+Это обработчик для шага 5 на диаграмме последовательности.
+
+```typescript
+ const requestId = nonces[req.params.nonce]
+ if (requestId === undefined) {
+ res.send("Bad nonce")
+ return ;
+ }
+
+ nonces[req.params.nonce] = undefined
+```
+
+Получите ID запроса и удалите nonce из `nonces`, чтобы убедиться, что его нельзя использовать повторно.
+
+```typescript
+ try {
+```
+
+Поскольку существует так много способов, которыми подпись может быть недействительной, мы оборачиваем это в `try ...` блок `catch`, чтобы перехватить любые возникающие ошибки.
+
+```typescript
+ const validSignature = await verifyMessage({
+ address: req.params.account,
+ message: `${loginPrompt}${req.params.nonce}`,
+ signature: req.params.signature
+ })
+```
+
+Используйте [`verifyMessage`](https://viem.sh/docs/actions/public/verifyMessage#verifymessage) для реализации шага 5.5 на диаграмме последовательности.
+
+```typescript
+ if (!validSignature)
+ throw("Bad signature")
+ } catch (err) {
+ res.send("Error:" + err)
+ return ;
+ }
+```
+
+Остальная часть обработчика эквивалентна тому, что мы делали в обработчике `/loginSubmitted` ранее, за исключением одного небольшого изменения.
+
+```typescript
+ const loginResponse = await idp.createLoginResponse(
+ .
+ .
+ .
+ {
+ email: req.params.account + "@bad.email.address"
+ }
+ );
+```
+
+У нас нет фактического адреса электронной почты (мы получим его в следующем разделе), поэтому пока мы возвращаем адрес Ethereum и четко помечаем его как не являющийся адресом электронной почты.
+
+```typescript
+// Конечная точка IdP для запросов на вход
+idpRouter.post(`/login`,
+ async (req, res) => {
+ try {
+ // Обходной путь, потому что мне не удалось заставить parseLoginRequest работать.
+ // const loginRequest = await idp.parseLoginRequest(sp, 'post', req)
+ const samlRequest = xmlParser.parse(Buffer.from(req.body.SAMLRequest, 'base64').toString('utf-8'))
+ res.send(getSignaturePage(samlRequest["samlp:AuthnRequest"]["@_ID"]))
+ } catch (err) {
+ console.error('Error processing SAML response:', err);
+ res.status(400).send('SAML authentication failed');
+ }
+ }
+)
+```
+
+Вместо `getLoginPage` теперь используйте `getSignaturePage` в обработчике шага 3.
+
+## Получение адреса электронной почты
+
+Следующий шаг — получение адреса электронной почты, идентификатора, запрошенного поставщиком услуг. Для этого мы используем [Ethereum Attestation Service (EAS)](https://attest.org/).
+
+Самый простой способ получить аттестации — использовать [GraphQL API](https://docs.attest.org/docs/developer-tools/api). Мы используем этот запрос:
+
+```
+query GetAttestationsByRecipient {
+ attestations(
+ where: {
+ recipient: { equals: "${getAddress(ethAddr)}" }
+ schemaId: { equals: "0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977" }
+ }
+ take: 1
+ ) {
+ data
+ id
+ attester
+ }
+}
+```
+
+Этот [`schemaId`](https://optimism.easscan.org/schema/view/0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977) включает только адрес электронной почты. Этот запрос запрашивает аттестации этой схемы. Субъект аттестации называется `recipient`. Это всегда адрес Ethereum.
+
+Внимание: способ получения аттестаций здесь имеет две проблемы с безопасностью.
+
+- Мы обращаемся к конечной точке API, `https://optimism.easscan.org/graphql`, которая является централизованным компонентом. Мы можем получить атрибут `id`, а затем выполнить поиск он-чейн, чтобы проверить, что аттестация реальна, но конечная точка API все еще может подвергать цензуре аттестации, не сообщая нам о них.
+
+ Эту проблему можно решить: мы могли бы запустить собственную конечную точку GraphQL и получать аттестации из логов цепи, но это излишне для наших целей.
+
+- Мы не смотрим на личность аттестующего. Любой может предоставить нам ложную информацию. В реальной реализации у нас был бы набор доверенных аттестующих, и мы бы рассматривали только их аттестации.
+
+Чтобы увидеть это в действии, остановите существующие IdP и SP и выполните следующие команды:
+
+```sh
+git checkout email-address
+pnpm install
+pnpm start
+```
+
+Затем укажите свой адрес электронной почты. У вас есть два способа сделать это:
+
+- Импортируйте кошелек, используя приватный ключ, и используйте тестовый приватный ключ `0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80`.
+
+- Добавьте аттестацию для своего собственного адреса электронной почты:
+
+ 1. Перейдите к [схеме в обозревателе аттестаций](https://optimism.easscan.org/schema/view/0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977).
+
+ 2. Нажмите **Attest with Schema**.
+
+ 3. Введите свой адрес Ethereum в качестве получателя, свой адрес электронной почты в качестве адреса электронной почты и выберите **Onchain**. Затем нажмите **Make Attestation**.
+
+ 4. Одобрите транзакцию в своем кошельке. Вам понадобится немного ETH в [блокчейне Optimism](https://app.optimism.io/bridge/deposit), чтобы заплатить за газ.
+
+В любом случае, после этого перейдите по адресу [http://localhost:3000](http://localhost:3000) и следуйте инструкциям. Если вы импортировали тестовый приватный ключ, вы получите электронное письмо `test_addr_0@example.com`. Если вы использовали свой собственный адрес, это должно быть то, что вы аттестовали.
+
+### Подробное объяснение
+
+
+
+Новые шаги — это взаимодействие GraphQL, шаги 5.6 и 5.7.
+
+И снова, вот измененные части `idp.mts`.
+
+```typescript
+import { GraphQLClient } from 'graphql-request'
+import { SchemaEncoder } from '@ethereum-attestation-service/eas-sdk'
+```
+
+Импортируйте необходимые нам библиотеки.
+
+```typescript
+const graphqlEndpointUrl = "https://optimism.easscan.org/graphql"
+```
+
+Для каждого блокчейна существует [отдельная конечная точка](https://docs.attest.org/docs/developer-tools/api).
+
+```typescript
+const graphqlClient = new GraphQLClient(graphqlEndpointUrl, { fetch })
+```
+
+Создайте новый клиент `GraphQLClient`, который мы можем использовать для запросов к конечной точке.
+
+```typescript
+const graphqlSchema = 'string emailAddress'
+const graphqlEncoder = new SchemaEncoder(graphqlSchema)
+```
+
+GraphQL дает нам только непрозрачный объект данных с байтами. Чтобы понять его, нам нужна схема.
+
+```typescript
+const ethereumAddressToEmail = async ethAddr => {
+```
+
+Функция для получения адреса электронной почты из адреса Ethereum.
+
+```typescript
+ const query = `
+ query GetAttestationsByRecipient {
+```
+
+Это GraphQL-запрос.
+
+```typescript
+ attestations(
+```
+
+Мы ищем аттестации.
+
+```typescript
+ where: {
+ recipient: { equals: "${getAddress(ethAddr)}" }
+ schemaId: { equals: "0xfa2eff59a916e3cc3246f9aec5e0ca00874ae9d09e4678e5016006f07622f977" }
+ }
+```
+
+Аттестации, которые нам нужны, — это те, что находятся в нашей схеме, где получателем является `getAddress(ethAddr)`. Функция [`getAddress`](https://viem.sh/docs/utilities/getAddress#getaddress) гарантирует, что наш адрес имеет правильную [контрольную сумму](https://github.com/ethereum/ercs/blob/master/ERCS/erc-55.md). Это необходимо, поскольку GraphQL чувствителен к регистру. "0xBAD060A7", "0xBad060A7" и "0xbad060a7" — это разные значения.
+
+```typescript
+ take: 1
+```
+
+Независимо от того, сколько аттестаций мы найдем, нам нужна только первая.
+
+```typescript
+ ) {
+ data
+ id
+ attester
+ }
+ }`
+```
+
+Поля, которые мы хотим получить.
+
+- `attester`: адрес, который отправил аттестацию. Обычно это используется для принятия решения о том, доверять ли аттестации.
+- `id`: идентификатор аттестации. Вы можете использовать это значение, чтобы [прочитать аттестацию он-чейн](https://optimism.blockscout.com/address/0x4200000000000000000000000000000000000021?tab=read_proxy&source_address=0x4E0275Ea5a89e7a3c1B58411379D1a0eDdc5b088#0xa3112a64) и проверить, верна ли информация из запроса GraphQL.
+- `data`: данные схемы (в данном случае адрес электронной почты).
+
+```typescript
+ const queryResult = await graphqlClient.request(query)
+
+ if (queryResult.attestations.length == 0)
+ return "no_address@available.is"
+```
+
+Если аттестации нет, верните очевидно неверное значение, которое, однако, будет выглядеть действительным для поставщика услуг.
+
+```typescript
+ const attestationDataFields = graphqlEncoder.decodeData(queryResult.attestations[0].data)
+ return attestationDataFields[0].value.value
+}
+```
+
+Если значение есть, используйте `decodeData` для его декодирования. Нам не нужны предоставляемые метаданные, только само значение.
+
+```typescript
+ const loginResponse = await idp.createLoginResponse(
+ sp,
+ {
+ .
+ .
+ .
+ },
+ "post",
+ {
+ email: await ethereumAddressToEmail(req.params.account)
+ }
+ );
+```
+
+Используйте новую функцию для получения адреса электронной почты.
+
+## Что насчет децентрализации?
+
+В этой конфигурации пользователи не могут выдавать себя за кого-то другого, пока мы полагаемся на надежных аттестующих для сопоставления адресов Ethereum с адресами электронной почты. Однако наш поставщик удостоверений по-прежнему является централизованным компонентом. Любой, у кого есть приватный ключ поставщика удостоверений, может отправить ложную информацию поставщику услуг.
+
+Возможно, есть решение с использованием [многосторонних вычислений (MPC)](https://en.wikipedia.org/wiki/Secure_multi-party_computation). Я надеюсь написать об этом в будущем руководстве.
+
+## Заключение
+
+Внедрение стандарта входа в систему, такого как подписи Ethereum, сталкивается с проблемой «курицы и яйца». Поставщики услуг хотят привлечь как можно более широкий рынок. Пользователи хотят иметь возможность получать доступ к услугам, не беспокоясь о поддержке своего стандарта входа.
+Создание адаптеров, таких как Ethereum IdP, может помочь нам преодолеть это препятствие.
+
+[Больше моих работ смотрите здесь](https://cryptodocguy.pro/).
diff --git a/public/content/translations/ru/developers/tutorials/getting-started-with-ethereum-development-using-alchemy/index.md b/public/content/translations/ru/developers/tutorials/getting-started-with-ethereum-development-using-alchemy/index.md
new file mode 100644
index 00000000000..49f27578e51
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/getting-started-with-ethereum-development-using-alchemy/index.md
@@ -0,0 +1,156 @@
+---
+title: "Начало разработки Ethereum"
+description: "Это руководство для начинающих по разработке на Ethereum. Мы проведем вас от развертывания конечной точки API и выполнения запроса в командной строке до написания вашего первого скрипта web3! Опыт разработки на блокчейне не требуется!"
+author: "Elan Halpern"
+tags:
+ [
+ "javascript",
+ "ethers.js",
+ "узлы",
+ "запросы",
+ "alchemy"
+ ]
+skill: beginner
+lang: ru
+published: 2020-10-30
+source: Medium
+sourceUrl: https://medium.com/alchemy-api/getting-started-with-ethereum-development-using-alchemy-c3d6a45c567f
+---
+
+
+
+Это руководство для начинающих по разработке на Ethereum. В этом руководстве мы будем использовать [Alchemy](https://alchemyapi.io/), ведущую платформу для разработчиков блокчейна, которая обслуживает миллионы пользователей из 70 % лучших блокчейн-приложений, включая Maker, 0x, MyEtherWallet, Dharma и Kyber. Alchemy предоставит нам доступ к конечной точке API в сети Ethereum, чтобы мы могли читать и записывать транзакции.
+
+Мы проведем вас от регистрации в Alchemy до написания вашего первого скрипта web3! Опыт разработки на блокчейне не требуется!
+
+## 1. Зарегистрируйте бесплатный аккаунт Alchemy {#sign-up-for-a-free-alchemy-account}
+
+Создать аккаунт в Alchemy легко, [зарегистрируйтесь бесплатно здесь](https://auth.alchemy.com/).
+
+## 2. Создайте приложение Alchemy {#create-an-alchemy-app}
+
+Чтобы взаимодействовать с сетью Ethereum и использовать продукты Alchemy, вам понадобится ключ API для аутентификации ваших запросов.
+
+Вы можете [создать ключи API на панели управления](https://dashboard.alchemy.com/). Чтобы создать новый ключ, перейдите в раздел «Create App» (Создать приложение), как показано ниже:
+
+Отдельная благодарность [_ShapeShift_](https://shapeshift.com/) _за то, что позволили нам показать их панель управления!_
+
+
+
+Заполните данные в разделе «Create App» (Создать приложение), чтобы получить новый ключ. Здесь вы также можете увидеть приложения, которые вы создали ранее, и те, которые создала ваша команда. Получите существующие ключи, нажав «View Key» (Просмотреть ключ) для любого приложения.
+
+
+
+Вы также можете получить существующие ключи API, наведя курсор на «Apps» (Приложения) и выбрав одно из них. Здесь вы можете выбрать «View Key» (Просмотреть ключ), а также «Edit App» (Редактировать приложение), чтобы внести определенные домены в белый список, просмотреть несколько инструментов для разработчиков и аналитику.
+
+
+
+## 3. Сделайте запрос из командной строки {#make-a-request-from-the-command-line}
+
+Взаимодействуйте с блокчейном Ethereum через Alchemy, используя JSON-RPC и curl.
+
+Для запросов вручную мы рекомендуем взаимодействовать с `JSON-RPC` через `POST`-запросы. Просто передайте заголовок `Content-Type: application/json` и ваш запрос в качестве тела `POST`-запроса со следующими полями:
+
+- `jsonrpc`: версия JSON-RPC — в настоящее время поддерживается только `2.0`.
+- `method`: метод ETH API. [См. справочник по API.](https://docs.alchemyapi.io/documentation/alchemy-api-reference/json-rpc)
+- `params`: список параметров для передачи в метод.
+- `id`: идентификатор вашего запроса. Он будет возвращен в ответе, чтобы вы могли отслеживать, к какому запросу относится ответ.
+
+Вот пример, который можно запустить из командной строки, чтобы получить текущую цену на газ:
+
+```bash
+curl https://eth-mainnet.alchemyapi.io/v2/demo \
+-X POST \
+-H "Content-Type: application/json" \
+-d '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":73}'
+```
+
+_**ПРИМЕЧАНИЕ.** Замените [https://eth-mainnet.alchemyapi.io/v2/demo](https://eth-mainnet.alchemyapi.io/jsonrpc/demo) на свой собственный ключ API `https://eth-mainnet.alchemyapi.io/v2/**your-api-key`._
+
+**Результаты:**
+
+```json
+{ "id": 73,"jsonrpc": "2.0","result": "0x09184e72a000" // 10000000000000 }
+```
+
+## 4. Настройте свой клиент Web3 {#set-up-your-web3-client}
+
+**Если у вас уже есть клиент,** измените URL-адрес текущего провайдера узла на URL-адрес Alchemy с вашим ключом API: `“https://eth-mainnet.alchemyapi.io/v2/your-api-key"`
+
+**_ПРИМЕЧАНИЕ:_** Приведенные ниже скрипты необходимо запускать в **контексте node** или **сохранять в файл**, а не запускать из командной строки. Если у вас еще не установлены Node или npm, ознакомьтесь с этим кратким [руководством по настройке для Mac](https://app.gitbook.com/@alchemyapi/s/alchemy/guides/alchemy-for-macs).
+
+Существует множество [библиотек Web3](https://docs.alchemyapi.io/guides/getting-started#other-web3-libraries), которые можно интегрировать с Alchemy, однако мы рекомендуем использовать [Alchemy Web3](https://docs.alchemy.com/reference/api-overview), прямую замену web3.js, созданную и настроенную для бесперебойной работы с Alchemy. Это дает множество преимуществ, таких как автоматические повторные попытки и надежная поддержка WebSocket.
+
+Чтобы установить AlchemyWeb3.js, **перейдите в каталог вашего проекта** и выполните:
+
+**С помощью Yarn:**
+
+```
+yarn add @alch/alchemy-web3
+```
+
+**С помощью NPM:**
+
+```
+npm install @alch/alchemy-web3
+```
+
+Чтобы взаимодействовать с инфраструктурой узлов Alchemy, запустите в NodeJS или добавьте это в файл JavaScript:
+
+```js
+const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
+const web3 = createAlchemyWeb3(
+ "https://eth-mainnet.alchemyapi.io/v2/your-api-key"
+)
+```
+
+## 5. Напишите свой первый скрипт Web3! {#write-your-first-web3-script}
+
+Теперь, чтобы немного попрактиковаться в программировании на web3, мы напишем простой скрипт, который выводит номер последнего блока из основной сети Ethereum.
+
+**1. Если вы еще этого не сделали, создайте в своем терминале новый каталог проекта и перейдите в него с помощью cd:**
+
+```
+mkdir web3-example
+cd web3-example
+```
+
+**2. Установите зависимость Alchemy web3 (или любую другую web3) в свой проект, если вы еще этого не сделали:**
+
+```
+npm install @alch/alchemy-web3
+```
+
+**3. Создайте файл с именем `index.js` и добавьте в него следующее содержимое:**
+
+> В конечном итоге вы должны заменить `demo` своим ключом HTTP API от Alchemy.
+
+```js
+async function main() {
+ const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
+ const web3 = createAlchemyWeb3("https://eth-mainnet.alchemyapi.io/v2/demo")
+ const blockNumber = await web3.eth.getBlockNumber()
+ console.log("Последний номер блока: " + blockNumber)
+}
+main()
+```
+
+Не знакомы с асинхронностью? Ознакомьтесь с этой [статьей на Medium](https://medium.com/better-programming/understanding-async-await-in-javascript-1d81bb079b2c).
+
+**4.** Запустите его в терминале с помощью node\*\*
+
+```
+node index.js
+```
+
+**5.** Теперь вы должны увидеть вывод последнего номера блока в вашей консоли!\*\*
+
+```
+Последний номер блока: 11043912
+```
+
+**Ура! Поздравляем! Вы только что написали свой первый скрипт web3 с помощью Alchemy 🎉**
+
+Не знаете, что делать дальше? Попробуйте развернуть свой первый смарт-контракт и попрактиковаться в программировании на Solidity с помощью нашего [руководства по созданию смарт-контракта Hello World](https://www.alchemy.com/docs/hello-world-smart-contract) или проверьте свои знания о панели управления с помощью [демо-приложения Dashboard](https://docs.alchemyapi.io/tutorials/demo-app)!
+
+_[Зарегистрируйтесь в Alchemy бесплатно](https://auth.alchemy.com/), ознакомьтесь с нашей [документацией](https://www.alchemy.com/docs/), а чтобы быть в курсе последних новостей, подписывайтесь на нас в [Twitter](https://twitter.com/AlchemyPlatform)_.
diff --git a/public/content/translations/ru/developers/tutorials/guide-to-smart-contract-security-tools/index.md b/public/content/translations/ru/developers/tutorials/guide-to-smart-contract-security-tools/index.md
new file mode 100644
index 00000000000..57923345a2d
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/guide-to-smart-contract-security-tools/index.md
@@ -0,0 +1,102 @@
+---
+title: "Руководство по инструментам безопасности смарт-контрактов"
+description: "Обзор трех различных методов тестирования и анализа программ"
+author: "Trailofbits"
+lang: ru
+tags: [ "твердость", "Умные контракты", "безопасность" ]
+skill: intermediate
+published: 2020-09-07
+source: Building secure contracts
+sourceUrl: https://github.com/crytic/building-secure-contracts/tree/master/program-analysis
+---
+
+Мы будем использовать три различных метода тестирования и анализа программ:
+
+- **Статический анализ с помощью [Slither](/developers/tutorials/how-to-use-slither-to-find-smart-contract-bugs/).** Все пути программы аппроксимируются и анализируются одновременно через различные представления программы (например, граф потока управления).
+- **Фазинг с помощью [Echidna](/developers/tutorials/how-to-use-echidna-to-test-smart-contracts/).** Код выполняется с псевдослучайной генерацией транзакций. Фазер попытается найти последовательность транзакций для нарушения заданного свойства.
+- **Символическое выполнение с помощью [Manticore](/developers/tutorials/how-to-use-manticore-to-find-smart-contract-bugs/).** Метод формальной верификации, который преобразует каждый путь выполнения в математическую формулу, для которой можно проверить ограничения.
+
+Каждый метод имеет свои преимущества и недостатки и будет полезен в [конкретных случаях](#determining-security-properties):
+
+| Метод | Инструмент | Использование | Скорость | Пропущенные ошибки | Ложные срабатывания |
+| ------------------------ | ---------- | --------------------------- | -------- | ------------------ | ------------------- |
+| Статический анализ | Slither | CLI и скрипты | секунд | Средний уровень | Низкий уровень |
+| Фазинг | Echidna | Свойства Solidity | минут | Низкий уровень | Отсутствуют |
+| Символическое выполнение | Manticore | Свойства Solidity и скрипты | часа | Отсутствуют\* | Отсутствуют |
+
+\* если все пути исследованы без тайм-аута
+
+**Slither** анализирует контракты за считаные секунды, однако статический анализ может приводить к ложным срабатываниям и будет менее пригоден для сложных проверок (например, арифметических). Запускайте Slither через API для простого доступа к встроенным детекторам или через API для пользовательских проверок.
+
+Выполнение **Echidna** занимает несколько минут, и она выдает только истинно-положительные результаты. Echidna проверяет предоставленные пользователем свойства безопасности, написанные на Solidity. Она может пропускать ошибки, поскольку основана на случайном исследовании.
+
+**Manticore** выполняет самый "тяжеловесный" анализ. Как и Echidna, Manticore проверяет предоставленные пользователем свойства. Ее выполнение занимает больше времени, но она может доказать достоверность свойства и не сообщает о ложных срабатываниях.
+
+## Предлагаемый рабочий процесс {#suggested-workflow}
+
+Начните со встроенных детекторов Slither, чтобы убедиться, что простые ошибки отсутствуют сейчас и не появятся позже. Используйте Slither для проверки свойств, связанных с наследованием, зависимостями переменных и структурными проблемами. По мере роста кодовой базы используйте Echidna для тестирования более сложных свойств конечного автомата. Вернитесь к Slither для разработки пользовательских проверок для защиты, недоступной в Solidity, например, для защиты от переопределения функции. Наконец, используйте Manticore для целенаправленной проверки критически важных свойств безопасности, например, арифметических операций.
+
+- Используйте CLI Slither для выявления распространенных проблем
+- Используйте Echidna для тестирования высокоуровневых свойств безопасности вашего контракта
+- Используйте Slither для написания пользовательских статических проверок
+- Используйте Manticore, когда вам потребуется углубленная проверка критически важных свойств безопасности
+
+**Примечание о модульных тестах**. Модульные тесты необходимы для создания высококачественного программного обеспечения. Однако эти методы не очень подходят для поиска уязвимостей в системе безопасности. Обычно они используются для тестирования позитивного поведения кода (т. е. код работает так, как ожидалось в обычном контексте), в то время как уязвимости в системе безопасности, как правило, находятся в крайних случаях, которые разработчики не учли. В нашем исследовании десятков аудитов безопасности смарт-контрактов [покрытие модульными тестами не повлияло на количество или серьезность уязвимостей](https://blog.trailofbits.com/2019/08/08/246-findings-from-our-smart-contract-audits-an-executive-summary/), которые мы обнаружили в коде наших клиентов.
+
+## Определение свойств безопасности {#determining-security-properties}
+
+Чтобы эффективно тестировать и проверять ваш код, вы должны определить области, требующие внимания. Поскольку ваши ресурсы, затрачиваемые на безопасность, ограничены, важно определить слабые или наиболее ценные части вашей кодовой базы, чтобы оптимизировать ваши усилия. Моделирование угроз может помочь. Рассмотрите следующие материалы:
+
+- [Быстрая оценка рисков (Rapid Risk Assessments)](https://infosec.mozilla.org/guidelines/risk/rapid_risk_assessment.html) (наш предпочтительный подход при нехватке времени)
+- [Руководство по моделированию угроз для систем, ориентированных на данные (Guide to Data-Centric System Threat Modeling)](https://csrc.nist.gov/pubs/sp/800/154/ipd) (также известно как NIST 800-154)
+- [Моделирование угроз по Шостаку (Shostack threat modeling)](https://www.amazon.com/Threat-Modeling-Designing-Adam-Shostack/dp/1118809998)
+- [STRIDE](https://wikipedia.org/wiki/STRIDE_\(security\)) / [DREAD](https://wikipedia.org/wiki/DREAD_\(risk_assessment_model\))
+- [PASTA](https://wikipedia.org/wiki/Threat_model#P.A.S.T.A.)
+- [Использование утверждений (Use of Assertions)](https://blog.regehr.org/archives/1091)
+
+### Компоненты {#components}
+
+Знание того, что вы хотите проверить, также поможет вам выбрать правильный инструмент.
+
+Общие области, которые часто актуальны для смарт-контрактов, включают в себя:
+
+- **Конечный автомат.** Большинство контрактов можно представить в виде конечного автомата. Рассмотрите возможность проверки того, что (1) недопустимое состояние не может быть достигнуто, (2) если состояние является допустимым, оно может быть достигнуто и (3) никакое состояние не блокирует контракт.
+
+ - Echidna и Manticore — предпочтительные инструменты для тестирования спецификаций конечного автомата.
+
+- **Средства контроля доступа.** Если в вашей системе есть привилегированные пользователи (например, владелец, контроллеры и т. д.) вы должны убедиться, что (1) каждый пользователь может выполнять только разрешенные действия и (2) ни один пользователь не может заблокировать действия более привилегированного пользователя.
+
+ - Slither, Echidna и Manticore могут проверять правильность контроля доступа. Например, Slither может проверить, что только у функций из белого списка отсутствует модификатор onlyOwner. Echidna и Manticore полезны для более сложного контроля доступа, например, когда разрешение предоставляется только в том случае, если контракт достигает определенного состояния.
+
+- **Арифметические операции.** Проверка правильности арифметических операций имеет решающее значение. Использование `SafeMath` повсеместно — это хороший шаг для предотвращения переполнения/потери значимости, однако вам все равно нужно учитывать другие арифметические недостатки, включая проблемы с округлением и недостатки, которые блокируют контракт.
+
+ - Manticore — лучший выбор в этом случае. Echidna можно использовать, если арифметика выходит за рамки возможностей решателя SMT.
+
+- **Корректность наследования.** Контракты Solidity в значительной степени полагаются на множественное наследование. Можно легко допустить такие ошибки, как отсутствие вызова `super` в затеняющей функции и неправильно интерпретированный порядок линеаризации c3.
+
+ - Slither — это инструмент для обеспечения обнаружения этих проблем.
+
+- **Внешние взаимодействия.** Контракты взаимодействуют друг с другом, и некоторым внешним контрактам не следует доверять. Например, если ваш контракт полагается на внешние оракулы, останется ли он безопасным, если половина доступных оракулов будет скомпрометирована?
+
+ - Manticore и Echidna — лучший выбор для тестирования внешних взаимодействий с вашими контрактами. У Manticore есть встроенный механизм для создания заглушек для внешних контрактов.
+
+- **Соответствие стандартам.** Стандарты Ethereum (например, ERC20) имеют историю недостатков в своей конструкции. Помните об ограничениях стандарта, на котором вы строите.
+ - Slither, Echidna и Manticore помогут вам обнаружить отклонения от заданного стандарта.
+
+### Шпаргалка по выбору инструментов {#tool-selection-cheatsheet}
+
+| Компонент | Инструменты | Примеры программ |
+| ------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Конечный автомат | Echidna, Manticore | |
+| Контроль доступа | Slither, Echidna, Manticore | [Упражнение 2 для Slither](https://github.com/crytic/slither/blob/7f54c8b948c34fb35e1d61adaa1bd568ca733253/docs/src/tutorials/exercise2.md), [Упражнение 2 для Echidna](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/exercises/Exercise-2.md) |
+| Арифметические операции | Manticore, Echidna | [Упражнение 1 для Echidna](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/exercises/Exercise-1.md), [Упражнения 1–3 для Manticore](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/manticore/exercises) |
+| Корректность наследования | Slither | [Упражнение 1 для Slither](https://github.com/crytic/slither/blob/7f54c8b948c34fb35e1d61adaa1bd568ca733253/docs/src/tutorials/exercise1.md) |
+| Внешние взаимодействия | Manticore, Echidna | |
+| Соответствие стандартам | Slither, Echidna, Manticore | [`slither-erc`](https://github.com/crytic/slither/wiki/ERC-Conformance) |
+
+В зависимости от ваших целей необходимо будет проверить и другие области, но эти общие области внимания являются хорошей отправной точкой для любой системы смарт-контрактов.
+
+Наши публичные аудиты содержат примеры проверенных или протестированных свойств. Рекомендуем прочитать разделы `Automated Testing and Verification` (Автоматическое тестирование и проверка) следующих отчетов, чтобы ознакомиться с реальными свойствами безопасности:
+
+- [0x](https://github.com/trailofbits/publications/blob/master/reviews/0x-protocol.pdf)
+- [Balancer](https://github.com/trailofbits/publications/blob/master/reviews/BalancerCore.pdf)
diff --git a/public/content/translations/ru/developers/tutorials/hello-world-smart-contract-fullstack/index.md b/public/content/translations/ru/developers/tutorials/hello-world-smart-contract-fullstack/index.md
new file mode 100644
index 00000000000..f3aff06012e
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/hello-world-smart-contract-fullstack/index.md
@@ -0,0 +1,1544 @@
+---
+title: "Смарт-контракт Hello World для начинающих — полный стек"
+description: "Вводное руководство по написанию и развертыванию простого смарт-контракта на Ethereum."
+author: "nstrike2"
+tags:
+ [
+ "твердость",
+ "hardhat",
+ "alchemy",
+ "Умные контракты",
+ "развертывание",
+ "обозреватель блоков",
+ "интерфейс",
+ "транзакции"
+ ]
+skill: beginner
+lang: ru
+published: 2021-10-25
+---
+
+Это руководство для вас, если вы новичок в разработке блокчейна и не знаете, с чего начать или как развертывать смарт-контракты и взаимодействовать с ними. Мы рассмотрим создание и развертывание простого смарт-контракта в тестовой сети Goerli с использованием [MetaMask](https://metamask.io), [Solidity](https://docs.soliditylang.org/en/v0.8.0/), [Hardhat](https://hardhat.org) и [Alchemy](https://alchemy.com/eth).
+
+Для выполнения этого руководства вам понадобится аккаунт Alchemy. [Зарегистрируйте бесплатный аккаунт](https://www.alchemy.com/).
+
+Если у вас возникнут вопросы, обращайтесь в [Discord Alchemy](https://discord.gg/gWuC7zB)!
+
+## Часть 1. Создание и развертывание вашего смарт-контракта с помощью Hardhat {#part-1}
+
+### Подключение к сети Ethereum {#connect-to-the-ethereum-network}
+
+Существует много способов отправлять запросы в сеть Ethereum. Для простоты мы будем использовать бесплатный аккаунт на Alchemy, платформе для разработчиков блокчейна и API, которая позволяет нам взаимодействовать с блокчейном Ethereum без необходимости запускать собственный узел. Alchemy также имеет инструменты для разработчиков для мониторинга и аналитики; мы воспользуемся ими в этом руководстве, чтобы понять, что происходит «под капотом» при развертывании нашего смарт-контракта.
+
+### Создайте свое приложение и ключ API {#create-your-app-and-api-key}
+
+После создания аккаунта Alchemy вы можете сгенерировать ключ API, создав приложение. Это позволит вам делать запросы к тестовой сети Goerli. Если вы не знакомы с тестовыми сетями, вы можете [прочитать руководство Alchemy по выбору сети](https://www.alchemy.com/docs/choosing-a-web3-network).
+
+На панели инструментов Alchemy найдите выпадающее меню **Приложения** на панели навигации и нажмите **Создать приложение**.
+
+
+
+Дайте вашему приложению имя «_Hello World_» и напишите краткое описание. Выберите **Staging** в качестве среды и **Goerli** в качестве сети.
+
+
+
+_Примечание: обязательно выберите **Goerli**, иначе это руководство не будет работать._
+
+Нажмите **Создать приложение**. Ваше приложение появится в таблице ниже.
+
+### Создайте аккаунт Ethereum {#create-an-ethereum-account}
+
+Вам нужен аккаунт Ethereum для отправки и получения транзакций. Мы будем использовать MetaMask, виртуальный кошелек в браузере, который позволяет пользователям управлять адресом своего аккаунта Ethereum.
+
+Вы можете бесплатно скачать и создать аккаунт MetaMask [здесь](https://metamask.io/download). При создании аккаунта или если у вас уже есть аккаунт, убедитесь, что вы переключились на «тестовую сеть Goerli» в правом верхнем углу (чтобы мы не имели дело с реальными деньгами).
+
+### Шаг 4. Добавьте эфир из крана (Faucet) {#step-4-add-ether-from-a-faucet}
+
+Чтобы развернуть свой смарт-контракт в тестовой сети, вам понадобится немного тестовых ETH. Чтобы получить ETH в сети Goerli, перейдите в кран Goerli и введите адрес своего аккаунта Goerli. Обратите внимание, что краны Goerli в последнее время могут быть немного ненадежными — смотрите [страницу тестовых сетей](/developers/docs/networks/#goerli) для списка вариантов, которые можно попробовать:
+
+_Примечание: из-за перегрузки сети это может занять некоторое время._
+``
+
+### Шаг 5: Проверьте свой баланс {#step-5-check-your-balance}
+
+Чтобы перепроверить, что ETH находится в вашем кошельке, давайте сделаем запрос [eth_getBalance](https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_getbalance) с помощью [инструмента-компоновщика Alchemy](https://composer.alchemyapi.io/?composer_state=%7B%22network%22%3A0%2C%22methodName%22%3A%22eth_getBalance%22%2C%22paramValues%22%3A%5B%22%22%2C%22latest%22%5D%7D). Результат будет содержать сумму ETH в нашем кошельке. Чтобы узнать больше, посмотрите [короткое руководство от Alchemy о том, как использовать инструмент-компоновщик](https://youtu.be/r6sjRxBZJuU).
+
+Введите адрес своего аккаунта MetaMask и нажмите **Отправить запрос**. Вы увидите ответ, похожий на фрагмент кода ниже.
+
+```json
+{ "jsonrpc": "2.0", "id": 0, "result": "0x2B5E3AF16B1880000" }
+```
+
+> _Примечание: этот результат указан в wei, а не в ETH._ _Wei используется как наименьшая единица эфира._
+
+Фух! Наши ненастоящие деньги уже все там.
+
+### Шаг 6: Инициализация нашего проекта {#step-6-initialize-our-project}
+
+Во-первых, нам нужно создать папку для нашего проекта. Перейдите в командную строку и введите следующее.
+
+```
+mkdir hello-world
+cd hello-world
+```
+
+Теперь, когда мы находимся в папке нашего проекта, мы будем использовать `npm init` для его инициализации.
+
+> Если у вас еще не установлен npm, следуйте [этим инструкциям по установке Node.js и npm](https://docs.alchemyapi.io/alchemy/guides/alchemy-for-macs#1-install-nodejs-and-npm).
+
+Для целей этого руководства не имеет значения, как вы ответите на вопросы инициализации. Вот как мы это сделали для примера:
+
+```
+имя пакета: (hello-world)
+версия: (1.0.0)
+описание: смарт-контракт hello world
+точка входа: (index.js)
+команда для тестирования:
+репозиторий git:
+ключевые слова:
+автор:
+лицензия: (ISC)
+
+Будет записано в /Users/.../.../.../hello-world/package.json:
+
+{
+ "name": "hello-world",
+ "version": "1.0.0",
+ "description": "смарт-контракт hello world",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
+```
+
+Подтвердите `package.json`, и мы готовы!
+
+### Шаг 7: Загрузка Hardhat {#step-7-download-hardhat}
+
+Hardhat - это среда для сборки, развертывания, тестирования и отладки программного обеспечения Ethereum. Он помогает разработчикам создавать смарт-контракты и децентрализованные приложения локально перед их развертыванием в основной сети.
+
+Внутри нашего проекта `hello-world` запустите:
+
+```
+npm install --save-dev hardhat
+```
+
+Более подробную информацию об [инструкциях по установке](https://hardhat.org/getting-started/#overview) можно найти на этой странице.
+
+### Шаг 8: Создание проекта Hardhat {#step-8-create-hardhat-project}
+
+В папке нашего проекта `hello-world` выполните:
+
+```
+npx hardhat
+```
+
+Вы увидите приветственное сообщение и интерфейс с вариантами того, что делать дальше. Выберите "create an empty hardhat.config.js":
+
+```
+888 888 888 888 888
+888 888 888 888 888
+888 888 888 888 888
+8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
+888 888 "88b 888P" d88" 888 888 "88b "88b 888
+888 888 .d888888 888 888 888 888 888 .d888888 888
+888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
+888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
+
+👷 Добро пожаловать в Hardhat v2.0.11 👷
+
+Что вы хотите сделать? …
+Создать пример проекта
+❯ Создать пустой hardhat.config.js
+Выйти
+```
+
+Это создаст файл `hardhat.config.js` в проекте. Мы будем использовать его позже в этом руководстве, чтобы указать настройки для нашего проекта.
+
+### Шаг 9: Добавление папок проекта {#step-9-add-project-folders}
+
+Чтобы поддерживать порядок в проекте, давайте создадим две новые папки. В командной строке перейдите в корневой каталог вашего проекта `hello-world` и введите:
+
+```
+mkdir contracts
+mkdir scripts
+```
+
+- `contracts/` — здесь мы будем хранить файл с кодом нашего смарт-контракта «hello world».
+- `scripts/` — здесь мы будем хранить скрипты для развертывания нашего контракта и взаимодействия с ним.
+
+### Шаг 10: Написание нашего контракта {#step-10-write-our-contract}
+
+Вы можете спросить себя, когда мы собираемся писать код? Время пришло!
+
+Откройте проект hello-world в своем любимом редакторе. Смарт-контракты чаще всего пишутся на Solidity, который мы и будем использовать для написания нашего смарт-контракта.
+
+1. Перейдите в папку `contracts` и создайте новый файл с именем `HelloWorld.sol`
+2. Ниже приведен пример смарт-контракта Hello World, который мы будем использовать в этом руководстве. Скопируйте содержимое ниже в файл `HelloWorld.sol`.
+
+_Примечание: обязательно прочитайте комментарии, чтобы понять, что делает этот контракт._
+
+```
+// Указывает версию Solidity, используя семантическое версионирование.
+// Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
+pragma solidity >=0.7.3;
+
+// Определяет контракт с именем `HelloWorld`.
+// Контракт — это набор функций и данных (его состояние). После развертывания контракт находится по определенному адресу в блокчейне Ethereum. Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
+contract HelloWorld {
+
+ // Генерируется при вызове функции update
+ //События смарт-контракта — это способ сообщить из вашего контракта во фронтенд вашего приложения о том, что что-то произошло в блокчейне. Фронтенд может «прослушивать» определенные события и предпринимать действия, когда они происходят.
+ event UpdatedMessages(string oldStr, string newStr);
+
+ // Объявляет переменную состояния `message` типа `string`.
+ // Переменные состояния — это переменные, значения которых постоянно хранятся в хранилище контракта. Ключевое слово `public` делает переменные доступными извне контракта и создает функцию, которую другие контракты или клиенты могут вызывать для доступа к значению.
+ string public message;
+
+ // Подобно многим объектно-ориентированным языкам на основе классов, конструктор — это специальная функция, которая выполняется только при создании контракта.
+ // Конструкторы используются для инициализации данных контракта. Узнайте больше:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
+ constructor(string memory initMessage) {
+
+ // Принимает строковый аргумент `initMessage` и устанавливает значение в переменную хранилища контракта `message`).
+ message = initMessage;
+ }
+
+ // Публичная функция, которая принимает строковый аргумент и обновляет переменную хранилища `message`.
+ function update(string memory newMessage) public {
+ string memory oldMsg = message;
+ message = newMessage;
+ emit UpdatedMessages(oldMsg, newMessage);
+ }
+}
+```
+
+Это базовый смарт-контракт, который хранит сообщение при создании. Его можно обновить, вызвав функцию `update`.
+
+### Шаг 11. Подключите MetaMask и Alchemy к вашему проекту {#step-11-connect-metamask-alchemy-to-your-project}
+
+Мы создали кошелек MetaMask, учетную запись Alchemy и написали наш смарт-контракт, теперь пришло время их соединить.
+
+Каждая транзакция, отправляемая из вашего кошелька, требует подписи с использованием вашего уникального приватного ключа. Чтобы предоставить нашей программе это разрешение, мы можем безопасно хранить наш приватный ключ в файле среды. Мы также будем хранить здесь ключ API для Alchemy.
+
+> Чтобы узнать больше об отправке транзакций, ознакомьтесь с [этим руководством](https://www.alchemy.com/docs/hello-world-smart-contract#step-11-connect-metamask--alchemy-to-your-project) по отправке транзакций с помощью web3.
+
+Во-первых, установите dotenv, находясь в директории проекта:
+
+```
+npm install dotenv --save
+```
+
+Затем создайте файл `.env` в корневом каталоге проекта. Добавьте в него свой приватный ключ MetaMask и URL-адрес HTTP API Alchemy.
+
+Ваш файл среды должен называться `.env`, иначе он не будет распознан как файл среды.
+
+Не называйте его `process.env`, `.env-custom` или как-либо еще.
+
+- Следуйте [этим инструкциям](https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key), чтобы экспортировать свой приватный ключ.
+- Ниже показано, как получить URL-адрес HTTP API Alchemy
+
+
+
+Ваш `.env` должен выглядеть следующим образом:
+
+```
+API_URL = "https://eth-goerli.alchemyapi.io/v2/ваш-api-ключ"
+PRIVATE_KEY = "ваш-приватный-ключ-metamask"
+```
+
+Чтобы подключить их к нашему коду, мы будем ссылаться на эти переменные в нашем файле `hardhat.config.js` в шаге 13.
+
+### Шаг 12: Установите Ethers.js {#step-12-install-ethersjs}
+
+Ethers.js — это библиотека, которая упрощает взаимодействие и отправку запросов в Ethereum, оборачивая [стандартные методы JSON-RPC](https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc) в более удобные для пользователя методы.
+
+Hardhat позволяет нам интегрировать [плагины](https://hardhat.org/plugins/) для дополнительных инструментов и расширенной функциональности. Мы воспользуемся [плагином Ethers](https://hardhat.org/docs/plugins/official-plugins#hardhat-ethers) для развертывания контракта.
+
+В директории проекта запустите:
+
+```bash
+npm install --save-dev @nomiclabs/hardhat-ethers "ethers@^5.0.0"
+```
+
+### Шаг 13: Обновление hardhat.config.js {#step-13-update-hardhat-configjs}
+
+Мы добавили несколько зависимостей и плагинов, и теперь нам нужно обновить `hardhat.config.js`, чтобы наш проект знал обо всех них.
+
+Обновите ваш `hardhat.config.js`, чтобы он выглядел следующим образом:
+
+```javascript
+/**
+ * @type import('hardhat/config').HardhatUserConfig
+ */
+
+require("dotenv").config()
+require("@nomiclabs/hardhat-ethers")
+
+const { API_URL, PRIVATE_KEY } = process.env
+
+module.exports = {
+ solidity: "0.7.3",
+ defaultNetwork: "goerli",
+ networks: {
+ hardhat: {},
+ goerli: {
+ url: API_URL,
+ accounts: [`0x${PRIVATE_KEY}`],
+ },
+ },
+}
+```
+
+### Шаг 14: Компиляция нашего контракта {#step-14-compile-our-contract}
+
+Пора заставить это работать, давайте скомпилируем наш контракт. Задача `compile` — одна из встроенных задач hardhat.
+
+Запустите в командной строке:
+
+```bash
+npx hardhat compile
+```
+
+Вы можете получить предупреждение `SPDX license identifier not provided in source file`, но не стоит об этом беспокоиться — надеемся, все остальное выглядит хорошо! Если нет, вы всегда можете написать в [Discord-канал Alchemy](https://discord.gg/u72VCg3).
+
+### Шаг 15: Написание нашего скрипта развертывания {#step-15-write-our-deploy-script}
+
+Контракт написан, файл конфигурации корректен, пора писать скрипт развертывания.
+
+Перейдите в папку `scripts/`, создайте новый файл `deploy.js` и добавьте в него следующее содержимое:
+
+```javascript
+async function main() {
+ const HelloWorld = await ethers.getContractFactory("HelloWorld")
+
+ // Начинаем развертывание, возвращая promise, который разрешается в объект контракта
+ const hello_world = await HelloWorld.deploy("Hello World!")
+ console.log("Контракт развернут по адресу:", hello_world.address)
+}
+
+main()
+ .then(() => process.exit(0))
+ .catch((error) => {
+ console.error(error)
+ process.exit(1)
+ })
+```
+
+Hardhat отлично объясняет, что делает каждая из этих строк кода, в своем [руководстве по контрактам](https://hardhat.org/tutorial/testing-contracts.html#writing-tests), мы использовали их объяснения здесь.
+
+```javascript
+const HelloWorld = await ethers.getContractFactory("HelloWorld")
+```
+
+`ContractFactory` в ethers.js — это абстракция, используемая для развертывания новых смарт-контрактов, поэтому `HelloWorld` здесь — это [фабрика](https://en.wikipedia.org/wiki/Factory_\(object-oriented_programming\)) для экземпляров нашего контракта hello world. При использовании плагина `hardhat-ethers` экземпляры `ContractFactory` и `Contract` по умолчанию подключаются к первому подписанту (владельцу).
+
+```javascript
+const hello_world = await HelloWorld.deploy()
+```
+
+Вызов `deploy()` на `ContractFactory` запустит развертывание и вернет `Promise`, который разрешается в объект `Contract`. Это объект, который имеет метод для каждой из функций нашего смарт контракта.
+
+### Шаг 16: Разверните наш контракт {#step-16-deploy-our-contract}
+
+Мы наконец-то готовы развернуть наш смарт контракт! Перейдите в командную строку и запустите:
+
+```bash
+npx hardhat run scripts/deploy.js --network goerli
+```
+
+Вы должны увидеть что-то наподобие:
+
+```bash
+Контракт развернут по адресу: 0x6cd7d44516a20882cEa2DE9f205bF401c0d23570
+```
+
+**Пожалуйста, сохраните этот адрес**. Мы будем использовать его позже в этом руководстве.
+
+Если мы перейдем на [Etherscan для Goerli](https://goerli.etherscan.io) и поищем адрес нашего контракта, мы должны увидеть, что он был успешно развернут. Транзакция будет выглядеть примерно так:
+
+
+
+Адрес `From` должен совпадать с адресом вашего аккаунта MetaMask, а в адресе `To` будет указано **Создание контракта**. Если мы щелкнем по транзакции, то увидим адрес нашего контракта в поле `To`.
+
+
+
+Поздравляем! Вы только что развернули смарт-контракт в тестовой сети Ethereum.
+
+Чтобы понять, что происходит «под капотом», давайте перейдем на вкладку Explorer на нашей [панели инструментов Alchemy](https://dashboard.alchemy.com/explorer). Если у вас несколько приложений Alchemy, убедитесь, что вы отфильтровали их по приложению и выбрали **Hello World**.
+
+
+
+Здесь вы увидите несколько методов JSON-RPC, которые Hardhat/Ethers сделали для нас «под капотом», когда мы вызвали функцию `.deploy()`. Два важных метода здесь — это [`eth_sendRawTransaction`](https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_sendrawtransaction), который является запросом на запись нашего контракта в сеть Goerli, и [`eth_getTransactionByHash`](https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_gettransactionbyhash), который является запросом на чтение информации о нашей транзакции по заданному хэшу. Чтобы узнать больше об отправке транзакций, ознакомьтесь с [нашим руководством по отправке транзакций с помощью Web3](/developers/tutorials/sending-transactions-using-web3-and-alchemy/).
+
+## Часть 2. Взаимодействие с вашим смарт-контрактом {#part-2-interact-with-your-smart-contract}
+
+Теперь, когда мы успешно развернули смарт-контракт в сети Goerli, давайте научимся с ним взаимодействовать.
+
+### Создайте файл interact.js {#create-a-interactjs-file}
+
+Это файл, в котором мы напишем наш скрипт взаимодействия. Мы будем использовать библиотеку Ethers.js, которую вы ранее установили в Части 1.
+
+В папке `scripts/` создайте новый файл с именем `interact.js` и добавьте следующий код:
+
+```javascript
+// interact.js
+
+const API_KEY = process.env.API_KEY
+const PRIVATE_KEY = process.env.PRIVATE_KEY
+const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS
+```
+
+### Обновите ваш файл .env {#update-your-env-file}
+
+Мы будем использовать новые переменные среды, поэтому нам нужно определить их в файле `.env`, который [мы создали ранее](#step-11-connect-metamask-&-alchemy-to-your-project).
+
+Нам нужно будет добавить определение для нашего `API_KEY` Alchemy и `CONTRACT_ADDRESS`, по которому был развернут ваш смарт-контракт.
+
+Ваш файл `.env` должен выглядеть примерно так:
+
+```bash
+# .env
+
+API_URL = "https://eth-goerli.alchemyapi.io/v2/<ваш-api-ключ>"
+API_KEY = "<ваш-api-ключ>"
+PRIVATE_KEY = "<ваш-приватный-ключ-metamask>"
+CONTRACT_ADDRESS = "0x<адрес_вашего_контракта>"
+```
+
+### Получите ABI вашего контракта {#grab-your-contract-ABI}
+
+Наш [ABI (двоичный интерфейс приложения)](/glossary/#abi) контракта — это интерфейс для взаимодействия с нашим смарт-контрактом. Hardhat автоматически генерирует ABI и сохраняет его в `HelloWorld.json`. Чтобы использовать ABI, нам нужно будет разобрать его содержимое, добавив следующие строки кода в наш файл `interact.js`:
+
+```javascript
+// interact.js
+const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")
+```
+
+Если вы хотите увидеть ABI, вы можете вывести его в консоль:
+
+```javascript
+console.log(JSON.stringify(contract.abi))
+```
+
+Чтобы увидеть ваш ABI, выведенный в консоль, перейдите в терминал и выполните:
+
+```bash
+npx hardhat run scripts/interact.js
+```
+
+### Создайте экземпляр вашего контракта {#create-an-instance-of-your-contract}
+
+Для взаимодействия с нашим контрактом нам нужно создать экземпляр контракта в нашем коде. Чтобы сделать это с помощью Ethers.js, нам нужно будет работать с тремя концепциями:
+
+1. Provider (провайдер) — поставщик узлов, который дает вам доступ на чтение и запись в блокчейн.
+2. Signer (подписант) — представляет аккаунт Ethereum, который может подписывать транзакции.
+3. Contract (контракт) — объект Ethers.js, представляющий конкретный контракт, развернутый в сети.
+
+Мы будем использовать ABI контракта из предыдущего шага, чтобы создать наш экземпляр контракта:
+
+```javascript
+// interact.js
+
+// Провайдер
+const alchemyProvider = new ethers.providers.AlchemyProvider(
+ (network = "goerli"),
+ API_KEY
+)
+
+// Подписант
+const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
+
+// Контракт
+const helloWorldContract = new ethers.Contract(
+ CONTRACT_ADDRESS,
+ contract.abi,
+ signer
+)
+```
+
+Узнайте больше о провайдерах, подписантах и контрактах в [документации ethers.js](https://docs.ethers.io/v5/).
+
+### Прочитайте начальное сообщение {#read-the-init-message}
+
+Помните, как мы развертывали наш контракт с `initMessage = "Hello world!"`? Теперь мы собираемся прочитать это сообщение, хранящееся в нашем смарт-контракте, и вывести его в консоль.
+
+В JavaScript асинхронные функции используются при взаимодействии с сетями. Чтобы узнать больше об асинхронных функциях, [прочитайте эту статью на Medium](https://blog.bitsrc.io/understanding-asynchronous-javascript-the-event-loop-74cd408419ff).
+
+Используйте приведенный ниже код, чтобы вызвать функцию `message` в нашем смарт-контракте и прочитать начальное сообщение:
+
+```javascript
+// interact.js
+
+// ...
+
+async function main() {
+ const message = await helloWorldContract.message()
+ console.log("Сообщение: " + message)
+}
+main()
+```
+
+После запуска файла с помощью `npx hardhat run scripts/interact.js` в терминале мы должны увидеть следующий ответ:
+
+```
+Сообщение: Hello world!
+```
+
+Поздравляем! Вы только что успешно прочитали данные смарт-контракта из блокчейна Ethereum, так держать!
+
+### Обновите сообщение {#update-the-message}
+
+Вместо того, чтобы просто читать сообщение, мы также можем обновить сообщение, сохраненное в нашем смарт-контракте, с помощью функции `update`! Круто, не так ли?
+
+Чтобы обновить сообщение, мы можем напрямую вызвать функцию `update` на нашем созданном объекте Contract:
+
+```javascript
+// interact.js
+
+// ...
+
+async function main() {
+ const message = await helloWorldContract.message()
+ console.log("Сообщение: " + message)
+
+ console.log("Обновление сообщения...")
+ const tx = await helloWorldContract.update("Это новое сообщение.")
+ await tx.wait()
+}
+main()
+```
+
+Обратите внимание, что в строке 11 мы вызываем `.wait()` для возвращенного объекта транзакции. Это гарантирует, что наш скрипт дождется майнинга транзакции в блокчейне перед выходом из функции. Если вызов `.wait()` не включен, скрипт может не увидеть обновленное значение `message` в контракте.
+
+### Прочитайте новое сообщение {#read-the-new-message}
+
+Вы должны быть в состоянии повторить [предыдущий шаг](#read-the-init-message), чтобы прочитать обновленное значение `message`. Потратьте немного времени и посмотрите, сможете ли вы внести необходимые изменения, чтобы вывести это новое значение!
+
+Если вам нужна подсказка, вот как должен выглядеть ваш файл `interact.js` на данном этапе:
+
+```javascript
+// interact.js
+
+const API_KEY = process.env.API_KEY
+const PRIVATE_KEY = process.env.PRIVATE_KEY
+const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS
+
+const contract = require("../artifacts/contracts/HelloWorld.sol/HelloWorld.json")
+
+// провайдер - Alchemy
+const alchemyProvider = new ethers.providers.AlchemyProvider(
+ (network = "goerli"),
+ API_KEY
+)
+
+// подписант - вы
+const signer = new ethers.Wallet(PRIVATE_KEY, alchemyProvider)
+
+// экземпляр контракта
+const helloWorldContract = new ethers.Contract(
+ CONTRACT_ADDRESS,
+ contract.abi,
+ signer
+)
+
+async function main() {
+ const message = await helloWorldContract.message()
+ console.log("Сообщение: " + message)
+
+ console.log("Обновление сообщения...")
+ const tx = await helloWorldContract.update("это новое сообщение")
+ await tx.wait()
+
+ const newMessage = await helloWorldContract.message()
+ console.log("Новое сообщение: " + newMessage)
+}
+
+main()
+```
+
+Теперь просто запустите скрипт, и вы сможете увидеть старое сообщение, статус обновления и новое сообщение, выведенные в ваш терминал!
+
+`npx hardhat run scripts/interact.js --network goerli`
+
+```
+Сообщение: Hello World!
+Обновление сообщения...
+Новое сообщение: This is the new message.
+```
+
+Во время выполнения этого скрипта вы можете заметить, что шаг `Обновление сообщения...` занимает некоторое время перед загрузкой нового сообщения. Это связано с процессом майнинга; если вам интересно отслеживать транзакции во время их майнинга, посетите [мемпул Alchemy](https://dashboard.alchemyapi.io/mempool), чтобы увидеть статус транзакции. Если транзакция была отброшена, также полезно проверить [Etherscan для Goerli](https://goerli.etherscan.io) и найти хэш вашей транзакции.
+
+## Часть 3: Публикация вашего смарт-контракта на Etherscan {#part-3-publish-your-smart-contract-to-etherscan}
+
+Вы проделали всю тяжелую работу по воплощению вашего смарт-контракта в жизнь; теперь пришло время поделиться им со всем миром!
+
+Проверив свой смарт-контракт на Etherscan, любой сможет просмотреть ваш исходный код и взаимодействовать с вашим смарт-контрактом. Давайте начнем!
+
+### Шаг 1: Создайте ключ API в своей учетной записи Etherscan {#step-1-generate-an-api-key-on-your-etherscan-account}
+
+Ключ API Etherscan необходим для подтверждения того, что вы являетесь владельцем смарт-контракта, который пытаетесь опубликовать.
+
+Если у вас еще нет аккаунта Etherscan, [зарегистрируйте аккаунт](https://etherscan.io/register).
+
+После входа в систему найдите свое имя пользователя на панели навигации, наведите на него курсор и выберите кнопку **Мой профиль**.
+
+На странице вашего профиля вы должны увидеть боковую панель навигации. На боковой панели навигации выберите **API Keys**. Затем нажмите кнопку «Add», чтобы создать новый ключ API, назовите свое приложение **hello-world** и нажмите кнопку **Create New API Key**.
+
+Ваш новый ключ API должен появиться в таблице ключей API. Скопируйте ключ API в буфер обмена.
+
+Далее нам нужно добавить ключ API Etherscan в наш файл `.env`.
+
+После его добавления ваш файл `.env` должен выглядеть так:
+
+```javascript
+API_URL = "https://eth-goerli.alchemyapi.io/v2/ваш-api-ключ"
+PUBLIC_KEY = "ваш-публичный-адрес-аккаунта"
+PRIVATE_KEY = "ваш-приватный-адрес-аккаунта"
+CONTRACT_ADDRESS = "адрес-вашего-контракта"
+ETHERSCAN_API_KEY = "ваш-ключ-etherscan"
+```
+
+### Смарт-контракты, развернутые с помощью Hardhat {#hardhat-deployed-smart-contracts}
+
+#### Установите hardhat-etherscan {#install-hardhat-etherscan}
+
+Публикация вашего контракта на Etherscan с помощью Hardhat очень проста. Сначала вам нужно будет установить плагин `hardhat-etherscan`. `hardhat-etherscan` автоматически проверит исходный код смарт-контракта и ABI на Etherscan. Чтобы добавить его, в каталоге `hello-world` выполните:
+
+```text
+npm install --save-dev @nomiclabs/hardhat-etherscan
+```
+
+После установки включите следующее выражение вверху вашего `hardhat.config.js` и добавьте параметры конфигурации Etherscan:
+
+```javascript
+// hardhat.config.js
+
+require("dotenv").config()
+require("@nomiclabs/hardhat-ethers")
+require("@nomiclabs/hardhat-etherscan")
+
+const { API_URL, PRIVATE_KEY, ETHERSCAN_API_KEY } = process.env
+
+module.exports = {
+ solidity: "0.7.3",
+ defaultNetwork: "goerli",
+ networks: {
+ hardhat: {},
+ goerli: {
+ url: API_URL,
+ accounts: [`0x${PRIVATE_KEY}`],
+ },
+ },
+ etherscan: {
+ // Ваш ключ API для Etherscan
+ // Получите его на https://etherscan.io/
+ apiKey: ETHERSCAN_API_KEY,
+ },
+}
+```
+
+#### Подтвердите свой смарт-контракт на Etherscan {#verify-your-smart-contract-on-etherscan}
+
+Убедитесь, что все файлы сохранены и все переменные `.env` настроены правильно.
+
+Запустите задачу `verify`, передав адрес контракта и сеть, в которой он развернут:
+
+```text
+npx hardhat verify --network goerli АДРЕС_РАЗВЕРНУТОГО_КОНТРАКТА 'Hello World!'
+```
+
+Убедитесь, что `DEPLOYED_CONTRACT_ADDRESS` — это адрес вашего развернутого смарт-контракта в тестовой сети Goerli. Кроме того, последний аргумент (`'Hello World!'`) должен быть той же строковой величиной, которая использовалась [на этапе развертывания в части 1](#write-our-deploy-script).
+
+Если все пройдет хорошо, вы увидите следующее сообщение в своем терминале:
+
+```text
+Исходный код для контракта успешно отправлен
+contracts/HelloWorld.sol:HelloWorld по адресу 0xdeployed-contract-address
+для проверки на Etherscan. Ожидание результата проверки...
+
+
+Контракт HelloWorld успешно проверен на Etherscan.
+https://goerli.etherscan.io/address/<адрес-контракта>#contracts
+```
+
+Поздравляем! Код вашего смарт-контракта находится на Etherscan!
+
+### Проверьте свой смарт-контракт на Etherscan! {#check-out-your-smart-contract-on-etherscan}
+
+Когда вы перейдете по ссылке, указанной в вашем терминале, вы сможете увидеть код вашего смарт-контракта и ABI, опубликованные на Etherscan!
+
+**Урааа — ты сделал это, чемпион! Теперь любой может вызывать или записывать данные в ваш смарт-контракт! Нам не терпится увидеть, что вы создадите дальше!**
+
+## Часть 4 — Интеграция вашего смарт-контракта с фронтендом {#part-4-integrating-your-smart-contract-with-the-frontend}
+
+К концу этого руководства вы узнаете, как:
+
+- Подключить кошелек MetaMask к вашему децентрализованному приложению
+- Считывать данные с вашего смарт-контракта с помощью API [Alchemy Web3](https://docs.alchemy.com/alchemy/documentation/alchemy-web3)
+- Подписывать транзакции Ethereum с помощью MetaMask
+
+Для этого децентрализованного приложения мы будем использовать [React](https://react.dev/) в качестве нашего фронтенд-фреймворка; однако важно отметить, что мы не будем тратить много времени на разбор его основ, поскольку в основном мы сосредоточимся на внедрении функциональности Web3 в наш проект.
+
+В качестве предварительного условия вы должны иметь начальный уровень понимания React. В противном случае мы рекомендуем пройти официальное [руководство по введению в React](https://react.dev/learn).
+
+### Клонируйте стартовые файлы {#clone-the-starter-files}
+
+Сначала перейдите в [репозиторий GitHub hello-world-part-four](https://github.com/alchemyplatform/hello-world-part-four-tutorial), чтобы получить стартовые файлы для этого проекта, и клонируйте этот репозиторий на свой локальный компьютер.
+
+Откройте клонированный репозиторий локально. Обратите внимание, что он содержит две папки: `starter-files` и `completed`.
+
+- `starter-files` - **мы будем работать в этом каталоге**, мы подключим пользовательский интерфейс к вашему кошельку Ethereum и смарт-контракту, который мы опубликовали на Etherscan в [Части 3](#part-3).
+- `completed` содержит полностью завершенное руководство и должен использоваться только в качестве справочника, если вы застряли.
+
+Далее откройте свою копию `starter-files` в вашем любимом редакторе кода, а затем перейдите в папку `src`.
+
+Весь код, который мы напишем, будет находиться в папке `src`. Мы будем редактировать компонент `HelloWorld.js` и файлы JavaScript `util/interact.js`, чтобы придать нашему проекту функциональность Web3.
+
+### Ознакомьтесь со стартовыми файлами {#check-out-the-starter-files}
+
+Прежде чем мы начнем писать код, давайте рассмотрим, что нам предоставлено в стартовых файлах.
+
+#### Запустите ваш React-проект {#get-your-react-project-running}
+
+Начнем с запуска проекта React в нашем браузере. Прелесть React в том, что как только наш проект запускается в нашем браузере, любые сохраняемые нами изменения будут обновляться в реальном времени в нашем браузере.
+
+Чтобы запустить проект, перейдите в корневой каталог папки `starter-files` и запустите `npm install` в своем терминале, чтобы установить зависимости проекта:
+
+```bash
+cd starter-files
+npm install
+```
+
+После завершения установки выполните `npm start` в терминале:
+
+```bash
+npm start
+```
+
+Это должно открыть [http://localhost:3000/](http://localhost:3000/) в вашем браузере, где вы увидите интерфейс нашего проекта. Он должен состоять из одного поля (места для обновления сообщения, хранящегося в вашем смарт-контракте), кнопки «Подключить кошелек» и кнопки «Обновить».
+
+Если вы попробуете нажать на любую из кнопок, вы заметите, что они не работают — это потому, что нам все еще нужно запрограммировать их функциональность.
+
+#### Компонент `HelloWorld.js` {#the-helloworld-js-component}
+
+Давайте вернемся в папку `src` в нашем редакторе и откроем файл `HelloWorld.js`. Очень важно, чтобы мы понимали все в этом файле, поскольку это основной компонент React, над которым мы будем работать.
+
+В верхней части этого файла вы заметите, что у нас есть несколько операторов импорта, которые необходимы для запуска нашего проекта, включая библиотеку React, хуки useEffect и useState, некоторые элементы из `./util/interact.js` (мы опишем их более подробно в ближайшее время!) и логотип Alchemy.
+
+```javascript
+// HelloWorld.js
+
+import React from "react"
+import { useEffect, useState } from "react"
+import {
+ helloWorldContract,
+ connectWallet,
+ updateMessage,
+ loadCurrentMessage,
+ getCurrentWalletConnected,
+} from "./util/interact.js"
+
+import alchemylogo from "./alchemylogo.svg"
+```
+
+Далее у нас есть переменные состояния, которые мы будем обновлять после определенных событий.
+
+```javascript
+// HelloWorld.js
+
+//Переменные состояния
+const [walletAddress, setWallet] = useState("")
+const [status, setStatus] = useState("")
+const [message, setMessage] = useState("Нет подключения к сети.")
+const [newMessage, setNewMessage] = useState("")
+```
+
+Вот что представляет собой каждая из переменных:
+
+- `walletAddress` — строка, в которой хранится адрес кошелька пользователя
+- `status` — строка, в которой хранится полезное сообщение, которое помогает пользователю взаимодействовать с децентрализованным приложением
+- `message` — строка, в которой хранится текущее сообщение в смарт-контракте
+- `newMessage` — строка, в которой хранится новое сообщение, которое будет записано в смарт-контракт
+
+После переменных состояния вы увидите пять нереализованных функций: `useEffect`, `addSmartContractListener`, `addWalletListener`, `connectWalletPressed` и `onUpdatePressed`. Ниже мы объясним, что они делают:
+
+```javascript
+// HelloWorld.js
+
+//вызывается только один раз
+useEffect(async () => {
+ //TODO: реализовать
+}, [])
+
+function addSmartContractListener() {
+ //TODO: реализовать
+}
+
+function addWalletListener() {
+ //TODO: реализовать
+}
+
+const connectWalletPressed = async () => {
+ //TODO: реализовать
+}
+
+const onUpdatePressed = async () => {
+ //TODO: реализовать
+}
+```
+
+- [`useEffect`](https://legacy.reactjs.org/docs/hooks-effect.html) — это хук React, который вызывается после рендеринга вашего компонента. Поскольку в него передается пустой массив `[]` в качестве свойства (см. строку 4), он будет вызываться только при _первом_ рендеринге компонента. Здесь мы загрузим текущее сообщение, хранящееся в нашем смарт-контракте, вызовем наши прослушиватели смарт-контракта и кошелька и обновим наш пользовательский интерфейс, чтобы отразить, подключен ли уже кошелек.
+- `addSmartContractListener` — эта функция настраивает прослушиватель, который будет отслеживать событие `UpdatedMessages` нашего контракта HelloWorld и обновлять наш пользовательский интерфейс при изменении сообщения в нашем смарт-контракте.
+- `addWalletListener` — эта функция настраивает прослушиватель, который обнаруживает изменения в состоянии кошелька MetaMask пользователя, например, когда пользователь отключает свой кошелек или переключает адреса.
+- `connectWalletPressed` — эта функция будет вызываться для подключения кошелька MetaMask пользователя к нашему децентрализованному приложению.
+- `onUpdatePressed` — эта функция будет вызываться, когда пользователь захочет обновить сообщение, хранящееся в смарт-контракте.
+
+Ближе к концу этого файла находится пользовательский интерфейс нашего компонента.
+
+```javascript
+// HelloWorld.js
+
+//UI нашего компонента
+return (
+
+)
+```
+
+Если вы внимательно изучите этот код, вы заметите, где мы используем наши различные переменные состояния в нашем пользовательском интерфейсе:
+
+- В строках 6–12, если кошелек пользователя подключен (т. е. `walletAddress.length > 0`), мы отображаем усеченную версию `walletAddress` пользователя на кнопке с идентификатором «walletButton»; в противном случае она просто гласит «Подключить кошелек».
+- В строке 17 мы отображаем текущее сообщение, хранящееся в смарт-контракте, которое зафиксировано в строке `message`.
+- В строках 23–26 мы используем [контролируемый компонент](https://legacy.reactjs.org/docs/forms.html#controlled-components) для обновления нашей переменной состояния `newMessage` при изменении ввода в текстовом поле.
+
+В дополнение к нашим переменным состояния вы также увидите, что функции `connectWalletPressed` и `onUpdatePressed` вызываются при нажатии кнопок с идентификаторами `publishButton` и `walletButton` соответственно.
+
+Наконец, давайте рассмотрим, куда добавляется этот компонент `HelloWorld.js`.
+
+Если вы перейдете к файлу `App.js`, который является основным компонентом в React, выступающим в качестве контейнера для всех других компонентов, вы увидите, что наш компонент `HelloWorld.js` внедряется в строке 7.
+
+И последнее, но не менее важное: давайте рассмотрим еще один предоставленный вам файл — `interact.js`.
+
+#### Файл `interact.js` {#the-interact-js-file}
+
+Поскольку мы хотим следовать парадигме [M-V-C](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller), нам понадобится отдельный файл, который содержит все наши функции для управления логикой, данными и правилами нашего децентрализованного приложения, а затем мы сможем экспортировать эти функции в наш интерфейс (наш компонент `HelloWorld.js`).
+
+👆🏽Именно в этом и заключается цель нашего файла `interact.js`!
+
+Перейдите в папку `util` в вашем каталоге `src`, и вы заметите, что мы включили файл с именем `interact.js`, который будет содержать все наши функции и переменные для взаимодействия со смарт-контрактами и кошельками.
+
+```javascript
+// interact.js
+
+//export const helloWorldContract;
+
+export const loadCurrentMessage = async () => {}
+
+export const connectWallet = async () => {}
+
+const getCurrentWalletConnected = async () => {}
+
+export const updateMessage = async (message) => {}
+```
+
+Вы заметите, что вверху файла мы закомментировали объект `helloWorldContract`. Позже в этом руководстве мы раскомментируем этот объект и создадим экземпляр нашего смарт-контракта в этой переменной, который затем экспортируем в наш компонент `HelloWorld.js`.
+
+Четыре нереализованные функции после нашего объекта `helloWorldContract` делают следующее:
+
+- `loadCurrentMessage` — эта функция обрабатывает логику загрузки текущего сообщения, хранящегося в смарт-контракте. Она сделает вызов для _чтения_ в смарт-контракт Hello World с помощью [API Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3).
+- `connectWallet` — эта функция подключит MetaMask пользователя к нашему децентрализованному приложению.
+- `getCurrentWalletConnected` — эта функция проверит, подключен ли уже аккаунт Ethereum к нашему децентрализованному приложению при загрузке страницы, и соответствующим образом обновит наш пользовательский интерфейс.
+- `updateMessage` — эта функция обновит сообщение, хранящееся в смарт-контракте. Она сделает вызов для _записи_ в смарт-контракт Hello World, поэтому кошелек MetaMask пользователя должен будет подписать транзакцию Ethereum для обновления сообщения.
+
+Теперь, когда мы понимаем, с чем работаем, давайте разберемся, как читать из нашего смарт-контракта!
+
+### Шаг 3: Чтение из вашего смарт-контракта {#step-3-read-from-your-smart-contract}
+
+Чтобы читать из вашего смарт-контракта, вам нужно будет успешно настроить:
+
+- API-соединение с блокчейном Ethereum
+- Загруженный экземпляр вашего смарт-контракта
+- Функция для вызова функции вашего смарт-контракта
+- Прослушиватель для отслеживания обновлений, когда данные, которые вы читаете из смарт-контракта, изменяются
+
+Это может показаться большим количеством шагов, но не волнуйтесь! Мы проведем вас через каждый из них шаг за шагом! :\)
+
+#### Установите API-соединение с блокчейном Ethereum {#establish-an-api-connection-to-the-ethereum-chain}
+
+Итак, помните, как во второй части этого руководства мы использовали наш ключ [Alchemy Web3 для чтения из нашего смарт-контракта](https://docs.alchemy.com/alchemy/tutorials/hello-world-smart-contract/interacting-with-a-smart-contract#step-1-install-web3-library)? Вам также понадобится ключ Alchemy Web3 в вашем децентрализованном приложении для чтения из блокчейна.
+
+Если у вас его еще нет, сначала установите [Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3), перейдя в корневой каталог ваших `starter-files` и выполнив в терминале следующую команду:
+
+```text
+npm install @alch/alchemy-web3
+```
+
+[Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3) — это оболочка для [Web3.js](https://docs.web3js.org/), предоставляющая расширенные методы API и другие важные преимущества, облегчающие жизнь веб-разработчика. Он разработан с требованием минимальной настройки, поэтому вы можете сразу начать использовать его в своем приложении!
+
+Затем установите пакет [dotenv](https://www.npmjs.com/package/dotenv) в каталог вашего проекта, чтобы у нас было безопасное место для хранения нашего ключа API после того, как мы его получим.
+
+```text
+npm install dotenv --save
+```
+
+Для нашего децентрализованного приложения **мы будем использовать наш ключ API для Websockets**, а не наш ключ API для HTTP, так как это позволит нам настроить прослушиватель, который обнаруживает, когда меняется сообщение, хранящееся в смарт-контракте.
+
+Как только у вас будет ключ API, создайте файл `.env` в вашем корневом каталоге и добавьте в него свой URL-адрес для Websockets Alchemy. После этого ваш файл `.env` должен выглядеть следующим образом:
+
+```javascript
+REACT_APP_ALCHEMY_KEY = wss://eth-goerli.ws.alchemyapi.io/v2/<ключ>
+```
+
+Теперь мы готовы настроить нашу конечную точку Alchemy Web3 в нашем децентрализованном приложении! Давайте вернемся к нашему `interact.js`, который находится в нашей папке `util`, и добавим следующий код в начало файла:
+
+```javascript
+// interact.js
+
+require("dotenv").config()
+const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
+const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
+const web3 = createAlchemyWeb3(alchemyKey)
+
+//export const helloWorldContract;
+```
+
+Выше мы сначала импортировали ключ Alchemy из нашего файла `.env`, а затем передали наш `alchemyKey` в `createAlchemyWeb3`, чтобы установить нашу конечную точку Alchemy Web3.
+
+С этой готовой конечной точкой пришло время загрузить наш смарт-контракт!
+
+#### Загрузка вашего смарт-контракта Hello World {#loading-your-hello-world-smart-contract}
+
+Чтобы загрузить ваш смарт-контракт Hello World, вам понадобится его адрес контракта и ABI, оба из которых можно найти на Etherscan, если вы завершили [Часть 3 этого руководства.](/developers/tutorials/hello-world-smart-contract-fullstack/#part-3-publish-your-smart-contract-to-etherscan-part-3-publish-your-smart-contract-to-etherscan)
+
+#### Как получить ABI вашего контракта с Etherscan {#how-to-get-your-contract-abi-from-etherscan}
+
+Если вы пропустили часть 3 этого руководства, вы можете использовать контракт HelloWorld с адресом [0x6f3f635A9762B47954229Ea479b4541eAF402A6A](https://goerli.etherscan.io/address/0x6f3f635a9762b47954229ea479b4541eaf402a6a#code). Его ABI можно найти [здесь](https://goerli.etherscan.io/address/0x6f3f635a9762b47954229ea479b4541eaf402a6a#code).
+
+ABI контракта необходим для указания, какую функцию будет вызывать контракт, а также для обеспечения того, чтобы функция возвращала данные в ожидаемом формате. После того, как мы скопировали ABI нашего контракта, давайте сохраним его в виде файла JSON с именем `contract-abi.json` в вашем каталоге `src`.
+
+Ваш файл contract-abi.json должен храниться в вашей папке src.
+
+Вооружившись адресом нашего контракта, ABI и конечной точкой Alchemy Web3, мы можем использовать [метод contract](https://docs.web3js.org/api/web3-eth-contract/class/Contract), чтобы загрузить экземпляр нашего смарт-контракта. Импортируйте ABI вашего контракта в файл `interact.js` и добавьте адрес вашего контракта.
+
+```javascript
+// interact.js
+
+const contractABI = require("../contract-abi.json")
+const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"
+```
+
+Теперь мы наконец-то можем раскомментировать нашу переменную `helloWorldContract` и загрузить смарт-контракт с помощью нашей конечной точки AlchemyWeb3:
+
+```javascript
+// interact.js
+export const helloWorldContract = new web3.eth.Contract(
+ contractABI,
+ contractAddress
+)
+```
+
+Подводя итог, первые 12 строк вашего `interact.js` теперь должны выглядеть так:
+
+```javascript
+// interact.js
+
+require("dotenv").config()
+const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY
+const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
+const web3 = createAlchemyWeb3(alchemyKey)
+
+const contractABI = require("../contract-abi.json")
+const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A"
+
+export const helloWorldContract = new web3.eth.Contract(
+ contractABI,
+ contractAddress
+)
+```
+
+Теперь, когда мы загрузили наш контракт, мы можем реализовать нашу функцию `loadCurrentMessage`!
+
+#### Реализация `loadCurrentMessage` в вашем файле `interact.js` {#implementing-loadCurrentMessage-in-your-interact-js-file}
+
+Эта функция очень проста. Мы сделаем простой асинхронный вызов web3 для чтения из нашего контракта. Наша функция вернет сообщение, хранящееся в смарт-контракте:
+
+Обновите `loadCurrentMessage` в вашем файле `interact.js` до следующего:
+
+```javascript
+// interact.js
+
+export const loadCurrentMessage = async () => {
+ const message = await helloWorldContract.methods.message().call()
+ return message
+}
+```
+
+Поскольку мы хотим отображать этот смарт-контракт в нашем пользовательском интерфейсе, давайте обновим функцию `useEffect` в нашем компоненте `HelloWorld.js` до следующего:
+
+```javascript
+// HelloWorld.js
+
+//вызывается только один раз
+useEffect(async () => {
+ const message = await loadCurrentMessage()
+ setMessage(message)
+}, [])
+```
+
+Обратите внимание, мы хотим, чтобы наша `loadCurrentMessage` вызывалась только один раз во время первого рендеринга компонента. Вскоре мы реализуем `addSmartContractListener` для автоматического обновления пользовательского интерфейса после изменения сообщения в смарт-контракте.
+
+Прежде чем мы углубимся в наш прослушиватель, давайте посмотрим, что у нас есть на данный момент! Сохраните ваши файлы `HelloWorld.js` и `interact.js`, а затем перейдите по адресу [http://localhost:3000/](http://localhost:3000/)
+
+Вы заметите, что текущее сообщение больше не гласит «Нет подключения к сети». Вместо этого оно отражает сообщение, хранящееся в смарт-контракте. Отлично!
+
+#### Ваш пользовательский интерфейс теперь должен отражать сообщение, хранящееся в смарт-контракте {#your-UI-should-now-reflect-the-message-stored-in-the-smart-contract}
+
+Теперь о прослушивателе...
+
+#### Реализуйте `addSmartContractListener` {#implement-addsmartcontractlistener}
+
+Если вы вернетесь к файлу `HelloWorld.sol`, который мы написали в [Части 1 этой серии руководств](https://docs.alchemy.com/alchemy/tutorials/hello-world-smart-contract#step-10-write-our-contract), вы вспомните, что есть событие смарт-контракта под названием `UpdatedMessages`, которое генерируется после вызова функции `update` нашего смарт-контракта (см. строки 9 и 27):
+
+```javascript
+// HelloWorld.sol
+
+// Указывает версию Solidity, используя семантическое версионирование.
+// Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
+pragma solidity ^0.7.3;
+
+// Определяет контракт с именем `HelloWorld`.
+// Контракт — это набор функций и данных (его состояние). После развертывания контракт находится по определенному адресу в блокчейне Ethereum. Узнайте больше: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
+contract HelloWorld {
+
+ // Генерируется при вызове функции update
+ //События смарт-контракта — это способ сообщить из вашего контракта во фронтенд вашего приложения о том, что что-то произошло в блокчейне. Фронтенд может «прослушивать» определенные события и предпринимать действия, когда они происходят.
+ event UpdatedMessages(string oldStr, string newStr);
+
+ // Объявляет переменную состояния `message` типа `string`.
+ // Переменные состояния — это переменные, значения которых постоянно хранятся в хранилище контракта. Ключевое слово `public` делает переменные доступными извне контракта и создает функцию, которую другие контракты или клиенты могут вызывать для доступа к значению.
+ string public message;
+
+ // Подобно многим объектно-ориентированным языкам на основе классов, конструктор — это специальная функция, которая выполняется только при создании контракта.
+ // Конструкторы используются для инициализации данных контракта. Узнайте больше:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
+ constructor(string memory initMessage) {
+
+ // Принимает строковый аргумент `initMessage` и устанавливает значение в переменную хранилища контракта `message`).
+ message = initMessage;
+ }
+
+ // Публичная функция, которая принимает строковый аргумент и обновляет переменную хранилища `message`.
+ function update(string memory newMessage) public {
+ string memory oldMsg = message;
+ message = newMessage;
+ emit UpdatedMessages(oldMsg, newMessage);
+ }
+}
+```
+
+События смарт-контракта — это способ, которым ваш контракт сообщает вашему фронтенд-приложению о том, что что-то произошло (т. е. произошло _событие_) в блокчейне, которое может «прослушивать» определенные события и предпринимать действия, когда они происходят.
+
+Функция `addSmartContractListener` будет специально прослушивать событие `UpdatedMessages` нашего смарт-контракта Hello World и обновлять наш пользовательский интерфейс для отображения нового сообщения.
+
+Измените `addSmartContractListener` на следующее:
+
+```javascript
+// HelloWorld.js
+
+function addSmartContractListener() {
+ helloWorldContract.events.UpdatedMessages({}, (error, data) => {
+ if (error) {
+ setStatus("😥 " + error.message)
+ } else {
+ setMessage(data.returnValues[1])
+ setNewMessage("")
+ setStatus("🎉 Ваше сообщение было обновлено!")
+ }
+ })
+}
+```
+
+Давайте разберем, что происходит, когда прослушиватель обнаруживает событие:
+
+- Если при генерации события произойдет ошибка, она будет отражена в пользовательском интерфейсе через нашу переменную состояния `status`.
+- В противном случае мы будем использовать возвращенный объект `data`. `data.returnValues` — это массив, индексированный с нуля, где первый элемент массива хранит предыдущее сообщение, а второй — обновленное. В целом, при успешном событии мы установим нашу строку `message` на обновленное сообщение, очистим строку `newMessage` и обновим нашу переменную состояния `status`, чтобы отразить, что новое сообщение было опубликовано в нашем смарт-контракте.
+
+Наконец, давайте вызовем наш прослушиватель в нашей функции `useEffect`, чтобы он был инициализирован при первом рендеринге компонента `HelloWorld.js`. В целом, ваша функция `useEffect` должна выглядеть так:
+
+```javascript
+// HelloWorld.js
+
+useEffect(async () => {
+ const message = await loadCurrentMessage()
+ setMessage(message)
+ addSmartContractListener()
+}, [])
+```
+
+Теперь, когда мы можем читать из нашего смарт-контракта, было бы здорово разобраться, как в него записывать! Однако, чтобы записывать в наше децентрализованное приложение, у нас сначала должен быть подключенный к нему кошелек Ethereum.
+
+Итак, далее мы займемся настройкой нашего кошелька Ethereum (MetaMask), а затем подключим его к нашему децентрализованному приложению!
+
+### Шаг 4: Настройте свой кошелек Ethereum {#step-4-set-up-your-ethereum-wallet}
+
+Чтобы что-либо записать в блокчейн Ethereum, пользователи должны подписывать транзакции с помощью приватных ключей своего виртуального кошелька. В этом руководстве мы будем использовать [MetaMask](https://metamask.io/), виртуальный кошелек в браузере, используемый для управления адресом вашего аккаунта Ethereum, так как он делает подписание транзакций очень простым для конечного пользователя.
+
+Если вы хотите больше узнать о том, как работают транзакции в Ethereum, ознакомьтесь с [этой страницей](/developers/docs/transactions/) от Ethereum Foundation.
+
+#### Загрузите MetaMask {#download-metamask}
+
+Вы можете бесплатно скачать и создать аккаунт MetaMask [здесь](https://metamask.io/download). При создании аккаунта или если у вас уже есть аккаунт, убедитесь, что вы переключились на «тестовую сеть Goerli» в правом верхнем углу (чтобы мы не имели дело с реальными деньгами).
+
+#### Добавьте эфир из крана {#add-ether-from-a-faucet}
+
+Чтобы подписать транзакцию в блокчейне Ethereum, нам понадобится немного тестового Eth. Чтобы получить Eth, вы можете перейти на [FaucETH](https://fauceth.komputing.org) и ввести адрес своего аккаунта Goerli, нажать «Запросить средства», затем выбрать «Ethereum Testnet Goerli» в выпадающем списке и, наконец, снова нажать кнопку «Запросить средства». Вскоре после этого вы должны увидеть Eth в своей учетной записи MetaMask!
+
+#### Проверьте свой баланс {#check-your-balance}
+
+Чтобы дважды проверить наш баланс, давайте сделаем запрос [eth_getBalance](https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_getbalance), используя [инструмент для составления запросов от Alchemy](https://composer.alchemyapi.io/?composer_state=%7B%22network%22%3A0%2C%22methodName%22%3A%22eth_getBalance%22%2C%22paramValues%22%3A%5B%22%22%2C%22latest%22%5D%7D). Так сумма Eth вернется в наш кошелек. После ввода адреса вашего аккаунта MetaMask и нажатия «Send Request» вы должны увидеть примерно такой ответ:
+
+```text
+{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}
+```
+
+**ПРИМЕЧАНИЕ:** этот результат указан в wei, а не в ETH. Wei это наименьшая единица измерения эфира. Преобразование wei в eth: 1 eth = 10¹⁸ wei. Итак, если мы преобразуем 0xde0b6b3a7640000 в десятичное число, мы получим 1\*10¹⁸, что равно 1 eth.
+
+Фух! Наши ненастоящие деньги уже все там! 🤑
+
+### Шаг 5: Подключите MetaMask к вашему пользовательскому интерфейсу {#step-5-connect-metamask-to-your-UI}
+
+Теперь, когда наш кошелек MetaMask настроен, давайте подключим к нему наше децентрализованное приложение!
+
+#### Функция `connectWallet` {#the-connectWallet-function}
+
+В нашем файле `interact.js` мы реализуем функцию `connectWallet`, которую затем сможем вызвать в нашем компоненте `HelloWorld.js`.
+
+Давайте изменим `connectWallet` на следующее:
+
+```javascript
+// interact.js
+
+export const connectWallet = async () => {
+ if (window.ethereum) {
+ try {
+ const addressArray = await window.ethereum.request({
+ method: "eth_requestAccounts",
+ })
+ const obj = {
+ status: "👆🏽 Напишите сообщение в текстовом поле выше.",
+ address: addressArray[0],
+ }
+ return obj
+ } catch (err) {
+ return {
+ address: "",
+ status: "😥 " + err.message,
+ }
+ }
+ } else {
+ return {
+ address: "",
+ status: (
+
+
+
+ ),
+ }
+ }
+}
+```
+
+Так что же именно делает этот гигантский блок кода?
+
+Ну, во-первых, он проверяет, включен ли `window.ethereum` в вашем браузере.
+
+`window.ethereum` — это глобальный API, внедряемый MetaMask и другими поставщиками кошельков, который позволяет веб-сайтам запрашивать аккаунты пользователей Ethereum. В случае одобрения он может считывать данные из блокчейнов, к которым подключен пользователь, и предлагать пользователю подписывать сообщения и транзакции. Для получения дополнительной информации см. [документацию MetaMask](https://docs.metamask.io/guide/ethereum-provider.html#table-of-contents)!
+
+Если `window.ethereum` _отсутствует_, это означает, что MetaMask не установлен. В результате возвращается объект JSON, где возвращаемый `address` представляет собой пустую строку, а объект `status` JSX сообщает, что пользователь должен установить MetaMask.
+
+Если же `window.ethereum` _присутствует_, то здесь начинается самое интересное.
+
+Используя цикл try/catch, мы попытаемся подключиться к MetaMask, вызвав [`window.ethereum.request({ method: "eth_requestAccounts" });`](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts). Вызов этой функции откроет MetaMask в браузере, и пользователю будет предложено подключить свой кошелек к вашему децентрализованному приложению.
+
+- Если пользователь решит подключиться, `method: "eth_requestAccounts"` вернет массив, содержащий все адреса аккаунтов пользователя, подключенных к децентрализованному приложению. В целом, наша функция `connectWallet` вернет объект JSON, который содержит _первый_ `address` в этом массиве (см. строку 9) и сообщение `status`, предлагающее пользователю написать сообщение в смарт-контракт.
+- Если пользователь отклоняет подключение, то объект JSON будет содержать пустую строку для возвращаемого `address` и сообщение `status`, которое отражает, что пользователь отклонил подключение.
+
+Теперь, когда мы написали эту функцию `connectWallet`, следующим шагом является ее вызов в нашем компоненте `HelloWorld.js`.
+
+#### Добавьте функцию `connectWallet` в ваш компонент пользовательского интерфейса `HelloWorld.js` {#add-the-connectWallet-function-to-your-HelloWorld-js-ui-component}
+
+Перейдите к функции `connectWalletPressed` в `HelloWorld.js` и обновите ее до следующего:
+
+```javascript
+// HelloWorld.js
+
+const connectWalletPressed = async () => {
+ const walletResponse = await connectWallet()
+ setStatus(walletResponse.status)
+ setWallet(walletResponse.address)
+}
+```
+
+Обратите внимание, как большая часть нашей функциональности абстрагирована от нашего компонента `HelloWorld.js` из файла `interact.js`? Это значит, что мы соответствуем парадигме M-V-С!
+
+В `connectWalletPressed` мы просто делаем вызов `await` к нашей импортированной функции `connectWallet` и, используя ее ответ, обновляем наши переменные `status` и `walletAddress` через их хуки состояния.
+
+Теперь давайте сохраним оба файла (`HelloWorld.js` и `interact.js`) и протестируем наш пользовательский интерфейс.
+
+Откройте браузер на странице [http://localhost:3000/](http://localhost:3000/) и нажмите кнопку «Подключить кошелек» в правом верхнем углу страницы.
+
+Если у вас установлен MetaMask, вам будет предложено подключить кошелек к вашему децентрализованному приложению. Примите приглашение на подключение.
+
+Вы должны увидеть, что кнопка кошелька теперь показывает, что ваш адрес подключен! Даааааа 🔥
+
+Далее попробуйте обновить страницу... это странно. Кнопка нашего кошелька предлагает нам подключить MetaMask, хотя он уже подключен...
+
+Однако, не бойтесь! Мы легко можем решить эту проблему (поняли каламбур с адресом?). реализовав `getCurrentWalletConnected`, которая проверит, подключен ли уже адрес к нашему децентрализованному приложению, и соответствующим образом обновит наш пользовательский интерфейс!
+
+#### Функция `getCurrentWalletConnected` {#the-getcurrentwalletconnected-function}
+
+Обновите вашу функцию `getCurrentWalletConnected` в файле `interact.js` до следующего:
+
+```javascript
+// interact.js
+
+export const getCurrentWalletConnected = async () => {
+ if (window.ethereum) {
+ try {
+ const addressArray = await window.ethereum.request({
+ method: "eth_accounts",
+ })
+ if (addressArray.length > 0) {
+ return {
+ address: addressArray[0],
+ status: "👆🏽 Напишите сообщение в текстовом поле выше.",
+ }
+ } else {
+ return {
+ address: "",
+ status: "🦊 Подключитесь к MetaMask, используя кнопку в правом верхнем углу.",
+ }
+ }
+ } catch (err) {
+ return {
+ address: "",
+ status: "😥 " + err.message,
+ }
+ }
+ } else {
+ return {
+ address: "",
+ status: (
+
+
+
+ ),
+ }
+ }
+}
+```
+
+Этот код _очень_ похож на функцию `connectWallet`, которую мы только что написали в предыдущем шаге.
+
+Основное отличие состоит в том, что вместо вызова метода `eth_requestAccounts`, который открывает MetaMask для подключения пользователя к кошельку, мы вызываем метод `eth_accounts`, который просто возвращает массив с адресами MetaMask, которые в данный момент подключены к нашему децентрализованному приложению.
+
+Чтобы увидеть эту функцию в действии, давайте вызовем ее в нашей функции `useEffect` нашего компонента `HelloWorld.js`:
+
+```javascript
+// HelloWorld.js
+
+useEffect(async () => {
+ const message = await loadCurrentMessage()
+ setMessage(message)
+ addSmartContractListener()
+
+ const { address, status } = await getCurrentWalletConnected()
+ setWallet(address)
+ setStatus(status)
+}, [])
+```
+
+Обратите внимание: мы используем ответ на наш вызов `getCurrentWalletConnected` для обновления наших переменных состояния `walletAddress` и `status`.
+
+Теперь, когда вы добавили этот код, давайте попробуем обновить окно нашего браузера.
+
+Отличнооооо! На кнопке должно быть указано, что вы подключены, и показан предварительный просмотр адреса вашего подключенного кошелька — даже после обновления!
+
+#### Реализуйте `addWalletListener` {#implement-addwalletlistener}
+
+Последним шагом в настройке нашего кошелька в децентрализированном приложении является реализация прослушивателя кошелька, чтобы наш пользовательский интерфейс обновлялся при изменении состояния нашего кошелька, например, когда пользователь отключает или переключает учетные записи.
+
+В вашем файле `HelloWorld.js` измените вашу функцию `addWalletListener` следующим образом:
+
+```javascript
+// HelloWorld.js
+
+function addWalletListener() {
+ if (window.ethereum) {
+ window.ethereum.on("accountsChanged", (accounts) => {
+ if (accounts.length > 0) {
+ setWallet(accounts[0])
+ setStatus("👆🏽 Напишите сообщение в текстовом поле выше.")
+ } else {
+ setWallet("")
+ setStatus("🦊 Подключитесь к MetaMask, используя кнопку в правом верхнем углу.")
+ }
+ })
+ } else {
+ setStatus(
+
+ )
+ }
+}
+```
+
+Спорим, вам даже не нужна наша помощь, чтобы понять, что здесь происходит, но для полноты картины давайте быстро разберем это:
+
+- Сначала наша функция проверяет, включен ли `window.ethereum` (т. е. установлен ли MetaMask).
+ - Если нет, мы просто устанавливаем для нашей переменной состояния `status` строку JSX, которая предлагает пользователю установить MetaMask.
+ - Если он включен, мы устанавливаем прослушиватель `window.ethereum.on("accountsChanged")` в строке 3, который прослушивает изменения состояния в кошельке MetaMask, включая подключение пользователем дополнительного аккаунта к децентрализованному приложению, переключение аккаунтов или отключение аккаунта. Если подключен хотя бы один аккаунт, переменная состояния `walletAddress` обновляется как первый аккаунт в массиве `accounts`, возвращаемом прослушивателем. В противном случае `walletAddress` устанавливается как пустая строка.
+
+И последнее, но не менее важное: мы должны вызвать ее в нашей функции `useEffect`:
+
+```javascript
+// HelloWorld.js
+
+useEffect(async () => {
+ const message = await loadCurrentMessage()
+ setMessage(message)
+ addSmartContractListener()
+
+ const { address, status } = await getCurrentWalletConnected()
+ setWallet(address)
+ setStatus(status)
+
+ addWalletListener()
+}, [])
+```
+
+Вот и все! Мы успешно завершили программирование всей функциональности нашего кошелька! Теперь перейдем к нашей последней задаче: обновлению сообщения, хранящегося в нашем смарт-контракте!
+
+### Шаг 6: Реализуйте функцию `updateMessage` {#step-6-implement-the-updateMessage-function}
+
+Итак, друзья, мы на финишной прямой! В `updateMessage` вашего файла `interact.js` мы сделаем следующее:
+
+1. Убедитесь, что сообщение, которое мы хотим опубликовать в нашем смарт-контакте, является действительным
+2. Подписать нашу транзакцию с помощью MetaMask
+3. Вызвать эту функцию из нашего фронтенд-компонента `HelloWorld.js`
+
+Это не займет много времени; давайте завершим это децентрализованное приложение!
+
+#### Обработка ошибок ввода {#input-error-handling}
+
+Естественно, имеет смысл иметь некоторую обработку ошибок ввода в начале функции.
+
+Мы хотим, чтобы наша функция завершалась раньше, если не установлено расширение MetaMask, не подключен кошелек (т. е. переданный `address` является пустой строкой) или `message` является пустой строкой. Давайте добавим следующую обработку ошибок в `updateMessage`:
+
+```javascript
+// interact.js
+
+export const updateMessage = async (address, message) => {
+ if (!window.ethereum || address === null) {
+ return {
+ status:
+ "💡 Подключите свой кошелек MetaMask, чтобы обновить сообщение в блокчейне.",
+ }
+ }
+
+ if (message.trim() === "") {
+ return {
+ status: "❌ Ваше сообщение не может быть пустой строкой.",
+ }
+ }
+}
+```
+
+Теперь, когда у нас есть правильная обработка ошибок ввода, пришло время подписать транзакцию через MetaMask!
+
+#### Подписание нашей транзакции {#signing-our-transaction}
+
+Если вы уже знакомы с традиционными транзакциями Ethereum в web3, код, который мы напишем дальше, будет вам очень знаком. Ниже вашего кода обработки ошибок ввода добавьте следующее в `updateMessage`:
+
+```javascript
+// interact.js
+
+//настройка параметров транзакции
+const transactionParameters = {
+ to: contractAddress, // Обязательно, кроме случаев публикации контракта.
+ from: address, // должен совпадать с активным адресом пользователя.
+ data: helloWorldContract.methods.update(message).encodeABI(),
+}
+
+//подписание транзакции
+try {
+ const txHash = await window.ethereum.request({
+ method: "eth_sendTransaction",
+ params: [transactionParameters],
+ })
+ return {
+ status: (
+
+ ✅{" "}
+
+ Посмотрите статус вашей транзакции на Etherscan!
+
+
+ ℹ️ Как только транзакция будет проверена сетью, сообщение будет
+ обновлено автоматически.
+
+ ),
+ }
+} catch (error) {
+ return {
+ status: "😥 " + error.message,
+ }
+}
+```
+
+Давайте разберемся, что происходит. Сначала мы настраиваем параметры наших транзакций, где:
+
+- `to` указывает адрес получателя (наш смарт-контракт)
+- `from` указывает подписанта транзакции, переменную `address`, которую мы передали в нашу функцию
+- `data` содержит вызов метода `update` нашего смарт-контракта Hello World, получая нашу строковую переменную `message` в качестве входных данных
+
+Затем мы делаем вызов await, `window.ethereum.request`, где мы просим MetaMask подписать транзакцию. Обратите внимание, в строках 11 и 12 мы указываем наш метод eth, `eth_sendTransaction`, и передаем наши `transactionParameters`.
+
+На этом этапе MetaMask откроется в браузере и предложит пользователю подписать или отклонить транзакцию.
+
+- Если транзакция будет успешной, функция вернет объект JSON, где строковая JSX `status` предлагает пользователю проверить Etherscan для получения дополнительной информации о своей транзакции.
+- Если транзакция не удастся, функция вернет объект JSON, где строковая `status` передает сообщение об ошибке.
+
+В целом, наша функция `updateMessage` должна выглядеть так:
+
+```javascript
+// interact.js
+
+export const updateMessage = async (address, message) => {
+ //обработка ошибок ввода
+ if (!window.ethereum || address === null) {
+ return {
+ status:
+ "💡 Подключите свой кошелек MetaMask, чтобы обновить сообщение в блокчейне.",
+ }
+ }
+
+ if (message.trim() === "") {
+ return {
+ status: "❌ Ваше сообщение не может быть пустой строкой.",
+ }
+ }
+
+ //настройка параметров транзакции
+ const transactionParameters = {
+ to: contractAddress, // Обязательно, кроме случаев публикации контракта.
+ from: address, // должен совпадать с активным адресом пользователя.
+ data: helloWorldContract.methods.update(message).encodeABI(),
+ }
+
+ //подписание транзакции
+ try {
+ const txHash = await window.ethereum.request({
+ method: "eth_sendTransaction",
+ params: [transactionParameters],
+ })
+ return {
+ status: (
+
+ ✅{" "}
+
+ Посмотрите статус вашей транзакции на Etherscan!
+
+
+ ℹ️ Как только транзакция будет проверена сетью, сообщение будет
+ обновлено автоматически.
+
+ ),
+ }
+ } catch (error) {
+ return {
+ status: "😥 " + error.message,
+ }
+ }
+}
+```
+
+И последнее, но не менее важное: нам нужно подключить нашу функцию `updateMessage` к нашему компоненту `HelloWorld.js`.
+
+#### Подключите `updateMessage` к фронтенду `HelloWorld.js` {#connect-updatemessage-to-the-helloworld-js-frontend}
+
+Наша функция `onUpdatePressed` должна сделать вызов await к импортированной функции `updateMessage` и изменить переменную состояния `status`, чтобы отразить, удалась ли наша транзакция или нет:
+
+```javascript
+// HelloWorld.js
+
+const onUpdatePressed = async () => {
+ const { status } = await updateMessage(walletAddress, newMessage)
+ setStatus(status)
+}
+```
+
+Это очень чисто и просто. И угадайте что... ВАШЕ ДЕЦЕНТРАЛИЗОВАННОЕ ПРИЛОЖЕНИЕ ГОТОВО!!!
+
+Вперед, протестируйте кнопку **Обновить**!
+
+### Создайте свое собственное децентрализованное приложение {#make-your-own-custom-dapp}
+
+Ура, вы дошли до конца руководства! Подведем итоги, вы научились:
+
+- Подключить кошелек MetaMask к вашему проекту децентрализованного приложения
+- Считывать данные с вашего смарт-контракта с помощью API [Alchemy Web3](https://docs.alchemy.com/alchemy/documentation/alchemy-web3)
+- Подписывать транзакции Ethereum с помощью MetaMask
+
+Теперь вы полностью готовы применить навыки из этого руководства для создания своего собственного проекта децентрализованного приложения! Как всегда, если у вас есть какие-либо вопросы, не стесняйтесь обращаться к нам за помощью в [Discord Alchemy](https://discord.gg/gWuC7zB). 🧙♂️
+
+Как только вы завершите это руководство, дайте нам знать, как прошел ваш опыт или если у вас есть какие-либо отзывы, отметив нас в Twitter [@alchemyplatform](https://twitter.com/AlchemyPlatform)!
diff --git a/public/content/translations/ru/developers/tutorials/hello-world-smart-contract/index.md b/public/content/translations/ru/developers/tutorials/hello-world-smart-contract/index.md
new file mode 100644
index 00000000000..b13078a4658
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/hello-world-smart-contract/index.md
@@ -0,0 +1,367 @@
+---
+title: "Смарт-контракт Hello World для начинающих"
+description: "Вводное руководство по написанию и развертыванию простого смарт-контракта на Ethereum."
+author: "elanh"
+tags:
+ [
+ "твердость",
+ "hardhat",
+ "alchemy",
+ "Умные контракты",
+ "развертывание"
+ ]
+skill: beginner
+lang: ru
+published: 2021-03-31
+---
+
+Если вы новичок в разработке блокчейн-приложений и не знаете, с чего начать, или если вы просто хотите понять, как развертывать смарт-контракты и взаимодействовать с ними, это руководство для вас. Мы рассмотрим создание и развертывание простого смарт-контракта в тестовой сети Sepolia с помощью виртуального кошелька [MetaMask](https://metamask.io/), [Solidity](https://docs.soliditylang.org/en/v0.8.0/), [Hardhat](https://hardhat.org/) и [Alchemy](https://www.alchemy.com/eth) (не волнуйтесь, если вы еще не понимаете, что все это значит, мы объясним).
+
+Во [второй части](https://docs.alchemy.com/docs/interacting-with-a-smart-contract) этого руководства мы рассмотрим, как можно взаимодействовать с нашим смарт-контрактом после его развертывания, а в [третьей части](https://www.alchemy.com/docs/submitting-your-smart-contract-to-etherscan) мы расскажем, как опубликовать его на Etherscan.
+
+Если у вас на любом этапе возникнут вопросы, не стесняйтесь задавать их в [Discord-канале Alchemy](https://discord.gg/gWuC7zB)!
+
+## Шаг 1. Подключение к сети Ethereum {#step-1}
+
+Существует много способов отправлять запросы в сеть Ethereum. Для простоты мы будем использовать бесплатную учетную запись на Alchemy — это платформа для блокчейн-разработчиков и API, которая позволяет нам взаимодействовать с блокчейном Ethereum без необходимости запускать собственные узлы. Платформа также имеет инструменты для мониторинга и аналитики, которыми мы воспользуемся в этом руководстве, чтобы понять, что происходит «под капотом» при развертывании нашего смарт-контракта. Если у вас еще нет учетной записи Alchemy, [вы можете бесплатно зарегистрироваться здесь](https://dashboard.alchemy.com/signup).
+
+## Шаг 2. Создайте свое приложение (и ключ API) {#step-2}
+
+Как только регистрация в Alchemy завершена, можно создать приложение и таким образом сгенерировать ключ API. Это позволит нам делать запросы к тестовой сети Sepolia. Если вы не знакомы с тестовыми сетями, ознакомьтесь с [этой страницей](/developers/docs/networks/).
+
+1. Перейдите на страницу "Create new app" в вашей панели управления Alchemy, выбрав "Select an app" на панели навигации и нажав "Create new app"
+
+
+
+2. Назовите свое приложение "Hello World", добавьте краткое описание и выберите вариант использования, например "Infra & Tooling". Далее найдите "Ethereum" и выберите сеть.
+
+
+
+3. Нажмите "Next" для продолжения, затем “Create app”, и все готово! Ваше приложение должно появиться в раскрывающемся меню панели навигации, а ключ API будет доступен для копирования.
+
+## Шаг 3. Создайте учетную запись Ethereum (адрес) {#step-3}
+
+Нам нужен аккаунт Ethereum для того, чтобы отправлять и получать транзакции. В этом руководстве мы будем использовать MetaMask, виртуальный кошелек в браузере, используемый для управления адресом вашего аккаунта Ethereum. Подробнее о [транзакциях](/developers/docs/transactions/).
+
+Вы можете скачать MetaMask и бесплатно создать учетную запись Ethereum [здесь](https://metamask.io/download). При создании учетной записи или если она у вас уже есть, обязательно переключитесь на тестовую сеть "Sepolia" в раскрывающемся меню сетей (чтобы мы не работали с реальными деньгами).
+
+Если вы не видите Sepolia в списке, зайдите в меню, затем в раздел Advanced и прокрутите вниз, чтобы включить опцию "Show test networks". В меню выбора сети перейдите на вкладку "Custom", чтобы найти список тестовых сетей, и выберите "Sepolia".
+
+
+
+## Шаг 4. Получите эфир из крана {#step-4}
+
+Чтобы развернуть наш смарт-контракт в тестовой сети, нам понадобится немного тестового Eth. Чтобы получить ETH в сети Sepolia, вы можете перейти к [сведениям о сети Sepolia](/developers/docs/networks/#sepolia), чтобы просмотреть список различных кранов. Если один не работает, попробуйте другой, так как они иногда могут иссякать. Получение тестовых ETH может занять некоторое время из-за загруженности сети. Вскоре после этого вы увидите ETH в своем кошельке MetaMask!
+
+## Шаг 5. Проверьте свой баланс {#step-5}
+
+Чтобы дважды проверить наш баланс, давайте сделаем запрос [eth_getBalance](/developers/docs/apis/json-rpc/#eth_getbalance), используя [инструмент Alchemy Composer](https://sandbox.alchemy.com/?network=ETH_SEPOLIA&method=eth_getBalance&body.id=1&body.jsonrpc=2.0&body.method=eth_getBalance&body.params%5B0%5D=&body.params%5B1%5D=latest). Результат будет содержать сумму ETH в нашем кошельке. После ввода адреса вашего аккаунта MetaMask и нажатия «Send Request» вы должны увидеть примерно такой ответ:
+
+```json
+{ "jsonrpc": "2.0", "id": 0, "result": "0x2B5E3AF16B1880000" }
+```
+
+> **ПРИМЕЧАНИЕ.** Этот результат указан в wei, а не в ETH. Wei это наименьшая единица измерения эфира. Конвертация из wei в ETH: 1 eth = 1018 wei. Итак, если мы конвертируем 0x2B5E3AF16B1880000 в десятичную систему, мы получим 5\*10¹⁸, что равно 5 ETH.
+>
+> Фух! Наши тестовые деньги на месте .
+
+## Шаг 6. Инициализируйте наш проект {#step-6}
+
+Во-первых, надо создать директорию для нашего проекта. Перейдите в вашу командную строку и наберите:
+
+```
+mkdir hello-world
+cd hello-world
+```
+
+Теперь, когда мы находимся в папке нашего проекта, мы будем использовать `npm init` для его инициализации. Если у вас еще не установлен npm, следуйте [этим инструкциям](https://docs.alchemyapi.io/alchemy/guides/alchemy-for-macs#1-install-nodejs-and-npm) (нам также понадобится Node.js, так что скачайте и его!).
+
+```
+npm init
+```
+
+Неважно, как вы ответите на вопросы при установке. Вот как для примера сделали это мы:
+
+```
+имя пакета: (hello-world)
+версия: (1.0.0)
+описание: hello world smart contract
+точка входа: (index.js)
+тестовая команда:
+репозиторий git:
+ключевые слова:
+автор:
+лицензия: (ISC)
+О файле /Users/.../.../.../hello-world/package.json будет записано следующее:
+
+{
+ "name": "hello-world",
+ "version": "1.0.0",
+ "description": "hello world smart contract",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC"
+}
+```
+
+Подтвердите `package.json`, и мы готовы!
+
+## Шаг 7. Загрузите [Hardhat](https://hardhat.org/getting-started/#overview) {#step-7}
+
+Hardhat - это среда для сборки, развертывания, тестирования и отладки программного обеспечения Ethereum. Он помогает разработчикам создавать смарт-контракты и децентрализованные приложения локально перед их развертыванием в основной сети.
+
+Внутри нашего проекта `hello-world` запустите:
+
+```
+npm install --save-dev hardhat
+```
+
+Более подробную информацию об [инструкциях по установке](https://hardhat.org/getting-started/#overview) можно найти на этой странице.
+
+## Шаг 8. Создайте проект Hardhat {#step-8}
+
+В директории проекта запустите:
+
+```
+npx hardhat
+```
+
+Вы увидите приветственное сообщение и интерфейс с вариантами того, что делать дальше. Выберите "create an empty hardhat.config.js":
+
+```
+888 888 888 888 888
+888 888 888 888 888
+888 888 888 888 888
+8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
+888 888 "88b 888P" d88" 888 888 "88b "88b 888
+888 888 .d888888 888 888 888 888 888 .d888888 888
+888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
+888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
+
+👷 Добро пожаловать в Hardhat v2.0.11 👷?
+
+Что вы хотите сделать? …
+Создать пример проекта
+❯ Создать пустой hardhat.config.js
+Выйти
+```
+
+Эта команда создаст для нас файл `hardhat.config.js`, в котором мы укажем все настройки для нашего проекта (в шаге 13).
+
+## Шаг 9. Добавьте папки проекта {#step-9}
+
+Для поддержания порядка в проекте мы создадим две новые папки. Перейдите в командной строке в корневую директорию проекта и наберите:
+
+```
+mkdir contracts
+mkdir scripts
+```
+
+- `contracts/` — здесь мы будем хранить файл с кодом нашего смарт-контракта «hello world».
+- `scripts/` — здесь мы будем хранить скрипты для развертывания нашего контракта и взаимодействия с ним.
+
+## Шаг 10. Напишите наш контракт {#step-10}
+
+Вы, наверное, спрашиваете себя, когда же мы наконец начнем писать код?? Что ж, вот мы и на шаге 10.
+
+Откройте проект hello-world в вашем любимом редакторе (нам нравится [VSCode](https://code.visualstudio.com/)). Смарт-контракты пишутся на языке под названием Solidity, который мы и будем использовать для написания нашего смарт-контракта HelloWorld.sol.
+
+1. Перейдите в папку «contracts» и создайте новый файл с именем HelloWorld.sol
+2. Ниже приведен пример смарт-контракта Hello World от Ethereum Foundation, который мы будем использовать в этом руководстве. Скопируйте и вставьте приведенное ниже содержимое в ваш файл HelloWorld.sol и обязательно прочтите комментарии, чтобы понять, что делает этот контракт:
+
+```solidity
+// Указывает версию Solidity с использованием семантического версионирования.
+// Подробнее: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
+pragma solidity ^0.7.0;
+
+// Определяет контракт с именем `HelloWorld`.
+// Контракт — это набор функций и данных (его состояние). После развертывания контракт размещается по определенному адресу в блокчейне Ethereum. Подробнее: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
+contract HelloWorld {
+
+ // Объявляет переменную состояния `message` типа `string`.
+ // Переменные состояния — это переменные, значения которых постоянно хранятся в хранилище контракта. Ключевое слово `public` делает переменные доступными извне контракта и создает функцию, которую другие контракты или клиенты могут вызывать для доступа к значению.
+ string public message;
+
+ // Подобно многим объектно-ориентированным языкам на основе классов, конструктор — это специальная функция, которая выполняется только при создании контракта.
+ // Конструкторы используются для инициализации данных контракта. Подробнее: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
+ constructor(string memory initMessage) {
+
+ // Принимает строковый аргумент `initMessage` и устанавливает значение в переменной хранения контракта `message`).
+ message = initMessage;
+ }
+
+ // Публичная функция, которая принимает строковый аргумент и обновляет переменную хранения `message`.
+ function update(string memory newMessage) public {
+ message = newMessage;
+ }
+}
+```
+
+Это очень простой смарт-контракт, который сохраняет сообщение при создании и может быть обновлен путем вызова функции `update`.
+
+## Шаг 11. Подключите MetaMask и Alchemy к вашему проекту {#step-11}
+
+Мы создали кошелек MetaMask, учетную запись Alchemy и написали наш смарт-контракт, теперь пришло время их соединить.
+
+Каждая транзакция, сделанная с вашего виртуального кошелька, требует подпись с использованием вашего же персонального ключа. Чтобы дать программе такую возможность, мы можем сохранить наш приватный ключ (и ключ API Alchemy) в файле окружения.
+
+> Чтобы узнать больше об отправке транзакций, ознакомьтесь с [этим руководством по отправке транзакций с использованием web3](/developers/tutorials/sending-transactions-using-web3-and-alchemy/).
+
+Во-первых, установите dotenv, находясь в директории проекта:
+
+```
+npm install dotenv --save
+```
+
+Затем создайте файл `.env` в корневом каталоге нашего проекта и добавьте в него свой закрытый ключ MetaMask и URL-адрес HTTP API Alchemy.
+
+- Следуйте [этим инструкциям](https://support.metamask.io/configure/accounts/how-to-export-an-accounts-private-key/), чтобы экспортировать свой приватный ключ
+- Ниже показано, как получить URL-адрес HTTP API Alchemy
+
+
+
+Скопируйте URL-адрес API Alchemy
+
+Ваш `.env` должен выглядеть следующим образом:
+
+```
+API_URL = "https://eth-sepolia.g.alchemy.com/v2/ваш-ключ-api"
+PRIVATE_KEY = "ваш-приватный-ключ-metamask"
+```
+
+Чтобы подключить их к нашему коду, мы будем ссылаться на эти переменные в нашем файле `hardhat.config.js` в шаге 13.
+
+
+
+
+Не добавляйте .env в коммиты! Пожалуйста, никогда и никому не сообщайте и не показывайте содержимое файла .env, так как тем самым вы компрометируете свои секретные данные. Если вы используете систему контроля версий, добавьте .env в файл gitignore.
+
+
+
+
+## Шаг 12: Установите Ethers.js {#step-12-install-ethersjs}
+
+Ethers.js — это библиотека, которая упрощает взаимодействие и отправку запросов в Ethereum, оборачивая [стандартные методы JSON-RPC](/developers/docs/apis/json-rpc/) в более удобные для пользователя методы.
+
+Hardhat очень упрощает интеграцию [плагинов](https://hardhat.org/plugins/) для дополнительных инструментов и расширенной функциональности. Мы воспользуемся преимуществами [плагина Ethers](https://hardhat.org/docs/plugins/official-plugins#hardhat-ethers) для развертывания контракта ([Ethers.js](https://github.com/ethers-io/ethers.js/) имеет несколько очень удобных методов развертывания контрактов).
+
+В директории проекта запустите:
+
+```
+npm install --save-dev @nomiclabs/hardhat-ethers "ethers@^5.0.0"
+```
+
+Нам также потребуется ethers в нашем `hardhat.config.js` на следующем шаге.
+
+## Шаг 13: Обновите hardhat.config.js {#step-13-update-hardhatconfigjs}
+
+Мы добавили несколько зависимостей и плагинов, и теперь нам нужно обновить `hardhat.config.js`, чтобы наш проект знал обо всех них.
+
+Обновите ваш `hardhat.config.js`, чтобы он выглядел следующим образом:
+
+```
+require('dotenv').config();
+
+require("@nomiclabs/hardhat-ethers");
+const { API_URL, PRIVATE_KEY } = process.env;
+
+/**
+* @type import('hardhat/config').HardhatUserConfig
+*/
+module.exports = {
+ solidity: "0.7.3",
+ defaultNetwork: "sepolia",
+ networks: {
+ hardhat: {},
+ sepolia: {
+ url: API_URL,
+ accounts: [`0x${PRIVATE_KEY}`]
+ }
+ },
+}
+```
+
+## Шаг 14: Скомпилируйте наш контракт {#step-14-compile-our-contracts}
+
+Пора заставить это работать, давайте скомпилируем наш контракт. Задача `compile` — одна из встроенных задач hardhat.
+
+Запустите в командной строке:
+
+```
+npx hardhat compile
+```
+
+Вы можете получить предупреждение об `SPDX license identifier not provided in source file`, но не беспокойтесь об этом — надеюсь, все остальное выглядит хорошо! Если нет, вы всегда можете написать в [Discord-канал Alchemy](https://discord.gg/u72VCg3).
+
+## Шаг 15: Напишите наш скрипт развертывания {#step-15-write-our-deploy-scripts}
+
+Контракт написан, файл конфигурации корректен, пора писать скрипт развертывания.
+
+Перейдите в папку `scripts/`, создайте новый файл `deploy.js` и добавьте в него следующее содержимое:
+
+```
+async function main() {
+ const HelloWorld = await ethers.getContractFactory("HelloWorld");
+
+ // Start deployment, returning a promise that resolves to a contract object
+ const hello_world = await HelloWorld.deploy("Привет, мир!");
+ console.log("Контракт развернут по адресу:", hello_world.address);}
+
+main()
+ .then(() => process.exit(0))
+ .catch(error => {
+ console.error(error);
+ process.exit(1);
+ });
+```
+
+Hardhat отлично объясняет, что делает каждая из этих строк кода, в своем [руководстве по контрактам](https://hardhat.org/tutorial/testing-contracts.html#writing-tests), мы использовали их объяснения здесь.
+
+```
+const HelloWorld = await ethers.getContractFactory("HelloWorld");
+```
+
+`ContractFactory` в ethers.js — это абстракция, используемая для развертывания новых смарт-контрактов, так что `HelloWorld` здесь — это фабрика для экземпляров нашего контракта «hello world». При использовании плагина `hardhat-ethers` экземпляры `ContractFactory` и `Contract` по умолчанию подключаются к первому подписанту.
+
+```
+const hello_world = await HelloWorld.deploy();
+```
+
+Вызов `deploy()` на `ContractFactory` запустит развертывание и вернет `Promise`, который разрешается в `Contract`. Это объект, который имеет метод для каждой из функций нашего смарт контракта.
+
+## Шаг 16: Разверните наш контракт {#step-16-deploy-our-contract}
+
+Мы наконец-то готовы развернуть наш смарт контракт! Перейдите в командную строку и запустите:
+
+```
+npx hardhat run scripts/deploy.js --network sepolia
+```
+
+Вы должны увидеть что-то наподобие:
+
+```
+Контракт развернут по адресу: 0x6cd7d44516a20882cEa2DE9f205bF401c0d23570
+```
+
+Если мы перейдем на [Etherscan для Sepolia](https://sepolia.etherscan.io/) и выполним поиск по адресу нашего контракта, то увидим, что он был успешно развернут. Транзакция будет выглядеть примерно так:
+
+
+
+Адрес `From` должен совпадать с адресом вашей учетной записи MetaMask, а в адресе `To` будет указано «Contract Creation», но если мы нажмем на транзакцию, мы увидим адрес нашего контракта в поле `To`:
+
+
+
+Поздравляем! Вы только что развернули смарт-контракт в блокчейне Ethereum 🎉
+
+Чтобы понять, что происходит «под капотом», давайте перейдем на вкладку Explorer в нашей [панели управления Alchemy](https://dashboard.alchemyapi.io/explorer). Если у вас несколько приложений Alchemy, убедитесь, что вы отфильтровали их по приложению и выбрали «Hello World».
+
+
+Здесь вы увидите несколько вызовов JSON-RPC, которые Hardhat/Ethers сделали для нас «под капотом», когда мы вызывали функцию `.deploy()`. Здесь следует отметить два важных вызова: [`eth_sendRawTransaction`](https://www.alchemy.com/docs/node/abstract/abstract-api-endpoints/eth-send-raw-transaction), который является запросом на фактическую запись нашего контракта в сеть Sepolia, и [`eth_getTransactionByHash`](https://www.alchemy.com/docs/node/abstract/abstract-api-endpoints/eth-get-transaction-by-hash), который является запросом на чтение информации о нашей транзакции по заданному хешу (типичный шаблон при работе с
+транзакциями). Чтобы узнать больше об отправке транзакций, ознакомьтесь с этим руководством по [отправке транзакций с помощью Web3](/developers/tutorials/sending-transactions-using-web3-and-alchemy/)
+
+На этом первая часть этого руководства закончена, во второй части мы будем [взаимодействовать с нашим смарт-контрактом](https://www.alchemy.com/docs/interacting-with-a-smart-contract), обновив наше первоначальное сообщение, а в третьей части мы [опубликуем наш смарт-контракт на Etherscan](https://www.alchemy.com/docs/submitting-your-smart-contract-to-etherscan), чтобы каждый мог узнать, как с ним взаимодействовать.
+
+**Хотите узнать больше об Alchemy? Посетите наш [веб-сайт](https://www.alchemy.com/eth). Никогда не хотите пропускать обновления? Подпишитесь на нашу рассылку [здесь](https://www.alchemy.com/newsletter)! Обязательно присоединяйтесь к нашему [Discord](https://discord.gg/u72VCg3).**.
diff --git a/public/content/translations/ru/developers/tutorials/how-to-implement-an-erc721-market/index.md b/public/content/translations/ru/developers/tutorials/how-to-implement-an-erc721-market/index.md
new file mode 100644
index 00000000000..a1fc2be483c
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/how-to-implement-an-erc721-market/index.md
@@ -0,0 +1,145 @@
+---
+title: "Как внедрить рынок ERC-721"
+description: "Как поставить «токенизированные» товары на продажу на децентрализованную доску объявлений"
+author: "Alberto Cuesta Cañada"
+tags: [ "смарт-контракты", "erc-721", "solidity", "токенов" ]
+skill: intermediate
+lang: ru
+published: 2020-03-19
+source: Hackernoon
+sourceUrl: https://hackernoon.com/how-to-implement-an-erc721-market-1e1a32j9
+---
+
+В этой статье я покажу вам, как программировать Craigslist для блокчейна Ethereum.
+
+До Gumtree, Ebay and Craigslist, классифицированные доски в основном были сделаны из пробки или бумаги. В школьных коридорах были доски объявлений, газеты, уличные фонари, витрины магазинов.
+
+С появлением интернета все изменилось. Число людей, которые могли увидеть конкретную доску объявлений, увеличилось на много порядков. Благодаря этому рынки, которые они представляют, стали гораздо эффективнее и масштабировались до глобальных размеров. Ebay — это огромный бизнес, который берет свое начало от этих физических досок объявлений.
+
+С появлением блокчейна эти рынки снова изменятся, и я покажу вам, как именно.
+
+## Монетизация {#monetization}
+
+Бизнес-модель общедоступной блокчейн-доски объявлений должна будет отличаться от бизнес-модели Ebay и подобных компаний.
+
+Во-первых, есть [аспект децентрализации](/developers/docs/web2-vs-web3/). Существующие платформы должны обслуживать собственные серверы. Децентрализованная платформа поддерживается ее пользователями, поэтому стоимость эксплуатации основной платформы для ее владельца падает до нуля.
+
+Далее идет внешний интерфейс, веб-сайт или интерфейс, который предоставляет доступ к платформе. Здесь есть много вариантов. Владельцы платформы могут ограничивать доступ и заставлять всех использовать их интерфейс, взимая за это плату. Владельцы платформы также могут решить открыть доступ (Власть народу!) и позволить любому создавать интерфейсы для платформы. Или владельцы могут выбрать любой подход между этими двумя крайностями.
+
+_Бизнес-лидеры с более широким видением, чем у меня, будут знать, как это монетизировать. Все, что я вижу, — это то, что это отличается от существующего положения вещей и, вероятно, выгодно._
+
+Кроме того, есть аспект автоматизации и платежей. Некоторые вещи можно очень [эффективно токенизировать](https://hackernoon.com/tokenization-of-digital-assets-g0ffk3v8s?ref=hackernoon.com) и продавать на доске объявлений. Токенизированные активы легко передаются в блокчейне. В блокчейне можно легко реализовать очень сложные методы оплаты.
+
+Я просто чую здесь возможность для бизнеса. Доска объявлений без эксплуатационных расходов может быть легко реализована, со сложными путями платежей, включенными в каждую транзакцию. Уверен, кто-нибудь придумает, для чего это можно использовать.
+
+Я просто рад это создавать. Давайте посмотрим на код.
+
+## Реализация {#implementation}
+
+Некоторое время назад мы создали [репозиторий с открытым исходным кодом](https://github.com/HQ20/contracts?ref=hackernoon.com) с примерами реализаций бизнес-кейсов и другими полезными вещами, пожалуйста, ознакомьтесь.
+
+Код для этой [доски объявлений Ethereum](https://github.com/HQ20/contracts/tree/master/contracts/classifieds?ref=hackernoon.com) находится там, пожалуйста, используйте его и злоупотребляйте им. Просто имейте в виду, что код не прошел аудит, и вам необходимо провести собственную комплексную проверку, прежде чем вкладывать в него деньги.
+
+Основы доски объявлений несложны. Все объявления на доске будут представлять собой просто структуру с несколькими полями:
+
+```solidity
+struct Trade {
+ address poster;
+ uint256 item;
+ uint256 price;
+ bytes32 status; // Открыта, Исполнена, Отменена
+}
+```
+
+Итак, кто-то размещает объявление. Товар на продажу. Цена за товар. Статус сделки, который может быть «открыта», «исполнена» или «отменена».
+
+Все эти сделки будут храниться в сопоставлении (mapping). Потому что в Solidity все, кажется, является сопоставлением (mapping). А также потому, что это удобно.
+
+```solidity
+mapping(uint256 => Trade) public trades;
+```
+
+Использование сопоставления (mapping) просто означает, что мы должны придумать идентификатор для каждого объявления перед его размещением, и нам нужно будет знать идентификатор объявления, прежде чем мы сможем с ним работать. Есть несколько способов справиться с этим либо в смарт-контракте, либо во внешнем интерфейсе. Пожалуйста, спросите, если вам нужны какие-либо подсказки.
+
+Далее возникает вопрос о том, с какими предметами мы имеем дело, и какая валюта используется для оплаты транзакции.
+
+Что касается предметов, мы просто попросим, чтобы они реализовывали интерфейс [ERC-721](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721.sol?ref=hackernoon.com), который на самом деле является просто способом представления предметов реального мира в блокчейне, хотя он [лучше всего работает с цифровыми активами](https://hackernoon.com/tokenization-of-digital-assets-g0ffk3v8s?ref=hackernoon.com). Мы собираемся указать наш собственный контракт ERC721 в конструкторе, что означает, что любые активы на нашей доске объявлений должны быть предварительно токенизированы.
+
+Для платежей мы собираемся сделать нечто подобное. Большинство блокчейн-проектов определяют свою собственную криптовалюту [ERC-20](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol?ref=hackernoon.com). Некоторые другие предпочитают использовать популярную валюту, например DAI. На этой доске объявлений вам просто нужно при создании решить, какой будет ваша валюта. Легко.
+
+```solidity
+constructor (
+ address _currencyTokenAddress, address _itemTokenAddress
+) public {
+ currencyToken = IERC20(_currencyTokenAddress);
+ itemToken = IERC721(_itemTokenAddress);
+ tradeCounter = 0;
+}
+```
+
+Мы почти у цели. У нас есть объявления, предметы для торговли и валюта для платежей. Разместить объявление означает поместить предмет на эскроу-счет, чтобы показать, что он у вас есть и что вы не разместили его дважды, возможно, на другой доске.
+
+Приведенный ниже код делает именно это. Помещает предмет на эскроу-счет, создает объявление, выполняет некоторые административные действия.
+
+```solidity
+function openTrade(uint256 _item, uint256 _price)
+ public
+{
+ itemToken.transferFrom(msg.sender, address(this), _item);
+ trades[tradeCounter] = Trade({
+ poster: msg.sender,
+ item: _item,
+ price: _price,
+ status: "Open"
+ });
+ tradeCounter += 1;
+ emit TradeStatusChange(tradeCounter - 1, "Open");
+}
+```
+
+Принять сделку означает выбрать объявление (сделку), оплатить цену, получить товар. Код ниже извлекает сделку. Проверяет ее доступность. Оплачивает товар. Получает товар. Обновляет объявление.
+
+```solidity
+function executeTrade(uint256 _trade)
+ public
+{
+ Trade memory trade = trades[_trade];
+ require(trade.status == "Open", "Сделка не открыта.");
+ currencyToken.transferFrom(msg.sender, trade.poster, trade.price);
+ itemToken.transferFrom(address(this), msg.sender, trade.item);
+ trades[_trade].status = "Executed";
+ emit TradeStatusChange(_trade, "Executed");
+}
+```
+
+Наконец, у нас есть возможность для продавцов отказаться от сделки до того, как ее примет покупатель. В некоторых моделях объявления вместо этого будут активны в течение определенного периода времени, прежде чем истечет их срок действия. Ваш выбор, в зависимости от дизайна вашего рынка.
+
+Код очень похож на тот, что используется для исполнения сделки, только валюта не переходит из рук в руки, а товар возвращается автору объявления.
+
+```solidity
+function cancelTrade(uint256 _trade)
+ public
+{
+ Trade memory trade = trades[_trade];
+ require(
+ msg.sender == trade.poster,
+ "Сделка может быть отменена только ее автором."
+ );
+ require(trade.status == "Open", "Сделка не открыта.");
+ itemToken.transferFrom(address(this), trade.poster, trade.item);
+ trades[_trade].status = "Cancelled";
+ emit TradeStatusChange(_trade, "Cancelled");
+}
+```
+
+Вот и все. Вы дошли до конца реализации. Удивительно, насколько компактными могут быть некоторые бизнес-концепции, выраженные в коде, и это один из таких случаев. Посмотрите полный контракт [в нашем репозитории](https://github.com/HQ20/contracts/blob/master/contracts/classifieds/Classifieds.sol).
+
+## Заключение {#conclusion}
+
+Доски объявлений — это распространенная конфигурация рынка, которая массово масштабировалась с появлением Интернета, став чрезвычайно популярной бизнес-моделью с несколькими монополистическими победителями.
+
+Доски объявлений также оказались простым инструментом для воспроизведения в среде блокчейна, с очень специфическими особенностями, которые сделают возможным вызов существующим гигантам.
+
+В этой статье я попытался соединить бизнес-реальность доски объявлений с технологической реализацией. Эти знания должны помочь вам создать видение и дорожную карту для реализации, если у вас есть необходимые навыки.
+
+Как всегда, если вы собираетесь создать что-то интересное и хотели бы получить совет, пожалуйста, [напишите мне](https://albertocuesta.es/)! Я всегда рад помочь.
diff --git a/public/content/translations/ru/developers/tutorials/how-to-mint-an-nft/index.md b/public/content/translations/ru/developers/tutorials/how-to-mint-an-nft/index.md
new file mode 100644
index 00000000000..8a2ff86fc91
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/how-to-mint-an-nft/index.md
@@ -0,0 +1,329 @@
+---
+title: "Как создать NFT (часть 2 из 3 серии руководств по NFT)"
+description: "Это руководство описывает, как создать NFT в блокчейне Ethereum, используя наш смарт-контракт и Web3."
+author: "Sumi Mudgil"
+tags: [ "ERC-721", "alchemy", "твердость", "Умные контракты" ]
+skill: beginner
+lang: ru
+published: 2021-04-22
+---
+
+[Beeple](https://www.nytimes.com/2021/03/11/arts/design/nft-auction-christies-beeple.html): 69 миллионов долларов
+[3LAU](https://www.forbes.com/sites/abrambrown/2021/03/03/3lau-nft-nonfungible-tokens-justin-blau/?sh=5f72ef64643b): 11 миллионов долларов
+[Grimes](https://www.theguardian.com/music/2021/mar/02/grimes-sells-digital-art-collection-non-fungible-tokens): 6 миллионов долларов
+
+Все они создали свои NFT, используя мощный API Alchemy. В этом руководстве мы научим вас, как сделать то же самое менее чем за 10 минут.
+
+«Создание NFT» — это акт публикации уникального экземпляра вашего токена ERC-721 в блокчейне. Используя наш смарт-контракт из [части 1 этой серии руководств по NFT](/developers/tutorials/how-to-write-and-deploy-an-nft/), давайте продемонстрируем наши навыки в Web3 и создадим NFT. В конце этого руководства вы сможете создать столько NFT, сколько душе (и кошельку) будет угодно!
+
+Приступим!
+
+## Шаг 1. Установите Web3 {#install-web3}
+
+Если вы следовали первому руководству по созданию смарт-контракта NFT, у вас уже есть опыт использования Ethers.js. Web3 похож на Ethers, так как это библиотека, используемая для упрощения создания запросов в блокчейн Ethereum. В этом руководстве мы будем использовать [Alchemy Web3](https://docs.alchemyapi.io/alchemy/documentation/alchemy-web3), которая является усовершенствованной библиотекой Web3, предлагающей автоматические повторные попытки и надежную поддержку WebSocket.
+
+В домашнем каталоге вашего проекта выполните:
+
+```
+npm install @alch/alchemy-web3
+```
+
+## Шаг 2. Создайте файл `mint-nft.js` {#create-mintnftjs}
+
+В каталоге `scripts` создайте файл `mint-nft.js` и добавьте следующие строки кода:
+
+```js
+require("dotenv").config()
+const API_URL = process.env.API_URL
+const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
+const web3 = createAlchemyWeb3(API_URL)
+```
+
+## Шаг 3. Получите ABI вашего контракта {#contract-abi}
+
+ABI (двоичный интерфейс приложения) нашего контракта — это интерфейс для взаимодействия с нашим смарт-контрактом. Подробнее об ABI контрактов можно узнать [здесь](https://docs.alchemyapi.io/alchemy/guides/eth_getlogs#what-are-ab-is). Hardhat автоматически генерирует для нас ABI и сохраняет его в файле `MyNFT.json`. Чтобы использовать его, нам нужно будет проанализировать содержимое, добавив следующие строки кода в наш файл `mint-nft.js`:
+
+```js
+const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json")
+```
+
+Если вы хотите увидеть ABI, вы можете вывести его в консоль:
+
+```js
+console.log(JSON.stringify(contract.abi))
+```
+
+Чтобы запустить `mint-nft.js` и увидеть свой ABI, выведенный в консоль, перейдите в терминал и выполните команду:
+
+```js
+node scripts/mint-nft.js
+```
+
+## Шаг 4. Настройте метаданные для вашего NFT с помощью IPFS {#config-meta}
+
+Если вы помните из нашего руководства в части 1, наша функция смарт-контракта `mintNFT` принимает параметр tokenURI, который должен преобразовываться в JSON-документ, описывающий метаданные NFT, — это то, что действительно оживляет NFT, позволяя ему иметь настраиваемые свойства, такие как имя, описание, изображение и другие атрибуты.
+
+> _Межпланетная файловая система (Interplanetary File System, IPFS) — это децентрализованный протокол и одноранговая сеть для хранения и обмена данными в распределенной файловой системе._
+
+Мы будем использовать Pinata, удобный API и инструментарий для IPFS, для хранения нашего NFT-актива и метаданных, чтобы гарантировать, что наш NFT действительно децентрализован. Если у вас нет аккаунта Pinata, зарегистрируйте бесплатный аккаунт [здесь](https://app.pinata.cloud) и выполните шаги для подтверждения вашей электронной почты.
+
+После создания аккаунта:
+
+- Перейдите на страницу «Файлы» и нажмите синюю кнопку «Загрузить» в левом верхнем углу страницы.
+
+- Загрузите изображение в Pinata — это будет графический актив для вашего NFT. Не стесняйтесь называть актив так, как вам хочется.
+
+- После загрузки вы увидите информацию о файле в таблице на странице «Файлы». Вы также увидите столбец CID. Вы можете скопировать CID, нажав на кнопку копирования рядом с ним. Вы можете просмотреть загруженный файл по адресу: `https://gateway.pinata.cloud/ipfs/`. Например, изображение, которое мы использовали, можно найти на IPFS [здесь](https://gateway.pinata.cloud/ipfs/QmZdd5KYdCFApWn7eTZJ1qgJu18urJrP9Yh1TZcZrZxxB5).
+
+Для тех, кто лучше воспринимает информацию визуально, вышеуказанные шаги кратко изложены здесь:
+
+
+
+Теперь нам нужно загрузить в Pinata еще один документ. Но прежде чем мы это сделаем, нам нужно его создать!
+
+В корневом каталоге создайте новый файл с именем `nft-metadata.json` и добавьте в него следующий JSON-код:
+
+```json
+{
+ "attributes": [
+ {
+ "trait_type": "Порода",
+ "value": "Мальтипу"
+ },
+ {
+ "trait_type": "Цвет глаз",
+ "value": "Мокко"
+ }
+ ],
+ "description": "Самый очаровательный и чувствительный щенок в мире.",
+ "image": "ipfs://QmWmvTJmJU3pozR9ZHFmQC2DNDwi2XJtf3QGyYiiagFSWb",
+ "name": "Рамзес"
+}
+```
+
+Вы можете изменять данные в JSON. Вы можете удалять или добавлять атрибуты в раздел `attributes`. Самое главное, убедитесь, что поле `image` указывает на местоположение вашего изображения в IPFS — в противном случае ваш NFT будет содержать фотографию (очень милой!) собаки.
+
+Завершив редактирование файла JSON, сохраните его и загрузите в Pinata, выполнив те же действия, что и при загрузке изображения.
+
+
+
+## Шаг 5. Создайте экземпляр вашего контракта {#instance-contract}
+
+Теперь, чтобы взаимодействовать с нашим контрактом, нам нужно создать его экземпляр в нашем коде. Для этого нам понадобится адрес нашего контракта, который мы можем получить из развертывания или [Blockscout](https://eth-sepolia.blockscout.com/), найдя адрес, который вы использовали для развертывания контракта.
+
+
+
+В приведенном выше примере адрес нашего контракта — 0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778.
+
+Далее мы будем использовать [метод contract](https://docs.web3js.org/api/web3-eth-contract/class/Contract) из Web3 для создания нашего контракта, используя ABI и адрес. В свой файл `mint-nft.js` добавьте следующее:
+
+```js
+const contractAddress = "0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778"
+
+const nftContract = new web3.eth.Contract(contract.abi, contractAddress)
+```
+
+## Шаг 6. Обновите файл `.env` {#update-env}
+
+Теперь для создания и отправки транзакций в сеть Ethereum мы будем использовать адрес вашего публичного аккаунта Ethereum, чтобы получить nonce аккаунта (объясним ниже).
+
+Добавьте свой публичный ключ в файл `.env` — если вы выполнили часть 1 руководства, ваш файл `.env` теперь должен выглядеть так:
+
+```js
+API_URL = "https://eth-sepolia.g.alchemy.com/v2/your-api-key"
+PRIVATE_KEY = "your-private-account-address"
+PUBLIC_KEY = "your-public-account-address"
+```
+
+## Шаг 7. Создайте свою транзакцию {#create-txn}
+
+Сначала давайте определим функцию с именем `mintNFT(tokenData)` и создадим нашу транзакцию, выполнив следующие действия:
+
+1. Возьмите _PRIVATE_KEY_ и _PUBLIC_KEY_ из файла `.env`.
+
+2. Далее нам нужно определить nonce аккаунта. Спецификация nonce используется для отслеживания количества транзакций, отправленных с вашего адреса, что необходимо в целях безопасности и для предотвращения [атак повторного воспроизведения](https://docs.alchemyapi.io/resources/blockchain-glossary#account-nonce). Чтобы получить количество транзакций, отправленных с вашего адреса, мы используем [getTransactionCount](https://docs.alchemyapi.io/documentation/alchemy-api-reference/json-rpc#eth_gettransactioncount).
+
+3. Наконец, мы настроим нашу транзакцию со следующей информацией:
+
+- `'from': PUBLIC_KEY` — источник нашей транзакции — наш публичный адрес.
+
+- `'to': contractAddress` — контракт, с которым мы хотим взаимодействовать и которому отправляем транзакцию.
+
+- `'nonce': nonce` — nonce аккаунта с количеством транзакций, отправленных с нашего адреса.
+
+- `'gas': estimatedGas` — расчетное количество газа, необходимое для завершения транзакции.
+
+- `'data': nftContract.methods.mintNFT(PUBLIC_KEY, md).encodeABI()` — вычисление, которое мы хотим выполнить в этой транзакции, — в данном случае это создание NFT.
+
+Теперь ваш файл `mint-nft.js` должен выглядеть следующим образом:
+
+```js
+ require('dotenv').config();
+ const API_URL = process.env.API_URL;
+ const PUBLIC_KEY = process.env.PUBLIC_KEY;
+ const PRIVATE_KEY = process.env.PRIVATE_KEY;
+
+ const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
+ const web3 = createAlchemyWeb3(API_URL);
+
+ const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json");
+ const contractAddress = "0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778";
+ const nftContract = new web3.eth.Contract(contract.abi, contractAddress);
+
+ async function mintNFT(tokenURI) {
+ const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, 'latest'); //получаем последний nonce
+
+ //транзакция
+ const tx = {
+ 'from': PUBLIC_KEY,
+ 'to': contractAddress,
+ 'nonce': nonce,
+ 'gas': 500000,
+ 'data': nftContract.methods.mintNFT(PUBLIC_KEY, tokenURI).encodeABI()
+ };
+ }
+```
+
+## Шаг 8. Подпишите транзакцию {#sign-txn}
+
+Теперь, когда мы создали нашу транзакцию, нам нужно подписать ее, чтобы отправить. Здесь мы будем использовать наш приватный ключ.
+
+`web3.eth.sendSignedTransaction` предоставит нам хэш транзакции, который мы можем использовать, чтобы убедиться, что наша транзакция была обработана и не была отброшена сетью. Вы заметите, что в разделе подписи транзакции мы добавили проверку ошибок, чтобы знать, успешно ли прошла наша транзакция.
+
+```js
+require("dotenv").config()
+const API_URL = process.env.API_URL
+const PUBLIC_KEY = process.env.PUBLIC_KEY
+const PRIVATE_KEY = process.env.PRIVATE_KEY
+
+const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
+const web3 = createAlchemyWeb3(API_URL)
+
+const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json")
+const contractAddress = "0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778"
+const nftContract = new web3.eth.Contract(contract.abi, contractAddress)
+
+async function mintNFT(tokenURI) {
+ const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest") //получаем последний nonce
+
+ //транзакция
+ const tx = {
+ from: PUBLIC_KEY,
+ to: contractAddress,
+ nonce: nonce,
+ gas: 500000,
+ data: nftContract.methods.mintNFT(PUBLIC_KEY, tokenURI).encodeABI(),
+ }
+
+ const signPromise = web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
+ signPromise
+ .then((signedTx) => {
+ web3.eth.sendSignedTransaction(
+ signedTx.rawTransaction,
+ function (err, hash) {
+ if (!err) {
+ console.log(
+ "Хэш вашей транзакции: ",
+ hash,
+ "\nПроверьте Mempool в Alchemy, чтобы просмотреть статус вашей транзакции!"
+ )
+ } else {
+ console.log(
+ "Что-то пошло не так при отправке вашей транзакции:",
+ err
+ )
+ }
+ }
+ )
+ })
+ .catch((err) => {
+ console.log(" Ошибка Promise:", err)
+ })
+}
+```
+
+## Шаг 9. Вызовите `mintNFT` и запустите `node mint-nft.js` {#call-mintnft-fn}
+
+Помните файл `metadata.json`, который вы загрузили в Pinata? Получите его хэш-код из Pinata и передайте в качестве параметра функции `mintNFT` следующее: `https://gateway.pinata.cloud/ipfs/`
+
+Вот как получить хэш-код:
+
+_Как получить хэш-код метаданных вашего NFT на Pinata_
+
+> Дважды проверьте, что скопированный вами хэш-код ссылается на ваш **metadata.json**, загрузив `https://gateway.pinata.cloud/ipfs/` в отдельном окне. Страница должна выглядеть примерно так же, как на скриншоте ниже:
+
+_На вашей странице должны отображаться метаданные JSON_
+
+В итоге ваш код должен выглядеть примерно так:
+
+```js
+require("dotenv").config()
+const API_URL = process.env.API_URL
+const PUBLIC_KEY = process.env.PUBLIC_KEY
+const PRIVATE_KEY = process.env.PRIVATE_KEY
+
+const { createAlchemyWeb3 } = require("@alch/alchemy-web3")
+const web3 = createAlchemyWeb3(API_URL)
+
+const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json")
+const contractAddress = "0x5a738a5c5fe46a1fd5ee7dd7e38f722e2aef7778"
+const nftContract = new web3.eth.Contract(contract.abi, contractAddress)
+
+async function mintNFT(tokenURI) {
+ const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, "latest") //получаем последний nonce
+
+ //транзакция
+ const tx = {
+ from: PUBLIC_KEY,
+ to: contractAddress,
+ nonce: nonce,
+ gas: 500000,
+ data: nftContract.methods.mintNFT(PUBLIC_KEY, tokenURI).encodeABI(),
+ }
+
+ const signPromise = web3.eth.accounts.signTransaction(tx, PRIVATE_KEY)
+ signPromise
+ .then((signedTx) => {
+ web3.eth.sendSignedTransaction(
+ signedTx.rawTransaction,
+ function (err, hash) {
+ if (!err) {
+ console.log(
+ "Хэш вашей транзакции: ",
+ hash,
+ "\nПроверьте Mempool в Alchemy, чтобы просмотреть статус вашей транзакции!"
+ )
+ } else {
+ console.log(
+ "Что-то пошло не так при отправке вашей транзакции:",
+ err
+ )
+ }
+ }
+ )
+ })
+ .catch((err) => {
+ console.log("Ошибка Promise:", err)
+ })
+}
+
+mintNFT("ipfs://QmYueiuRNmL4MiA2GwtVMm6ZagknXnSpQnB3z2gWbz36hP")
+```
+
+Теперь выполните `node scripts/mint-nft.js`, чтобы создать свой NFT. Через пару секунд вы должны увидеть в своем терминале примерно такой ответ:
+
+ ```
+ Хэш вашей транзакции: 0x301791fdf492001fcd9d5e5b12f3aa1bbbea9a88ed24993a8ab2cdae2d06e1e8
+
+ Проверьте Mempool в Alchemy, чтобы просмотреть статус вашей транзакции!
+ ```
+
+Затем посетите [mempool в Alchemy](https://dashboard.alchemyapi.io/mempool), чтобы увидеть статус вашей транзакции (ожидает ли она, обработана или отклонена сетью). Если ваша транзакция была отклонена, также полезно проверить [Blockscout](https://eth-sepolia.blockscout.com/) и найти хэш вашей транзакции.
+
+_Просмотр хэша транзакции вашего NFT на Etherscan_
+
+Вот и все! Вы развернули и создали NFT в блокчейне Ethereum
+
+С помощью `mint-nft.js` вы можете создать столько NFT, сколько душе (и кошельку) угодно! Только не забудьте передать новый tokenURI, описывающий метаданные NFT (иначе вы просто создадите кучу одинаковых NFT с разными идентификаторами).
+
+Предположительно, вы хотели бы иметь возможность похвастаться своим NFT в своем кошельке — так что обязательно ознакомьтесь с [Частью 3: Как просмотреть свой NFT в кошельке](/developers/tutorials/how-to-view-nft-in-metamask/)!
diff --git a/public/content/translations/ru/developers/tutorials/how-to-mock-solidity-contracts-for-testing/index.md b/public/content/translations/ru/developers/tutorials/how-to-mock-solidity-contracts-for-testing/index.md
new file mode 100644
index 00000000000..dc16056a430
--- /dev/null
+++ b/public/content/translations/ru/developers/tutorials/how-to-mock-solidity-contracts-for-testing/index.md
@@ -0,0 +1,108 @@
+---
+title: "Как имитировать умные контракты Solidity для тестирования"
+description: "Почему Вы должны получить удовольствие при тестировании Ваших контрактов"
+author: Markus Waas
+lang: ru
+tags:
+ [
+ "твердость",
+ "Умные контракты",
+ "тестирование",
+ "имитация"
+ ]
+skill: intermediate
+published: 2020-05-02
+source: soliditydeveloper.com
+sourceUrl: https://soliditydeveloper.com/mocking-contracts
+---
+
+[Мок-объекты](https://wikipedia.org/wiki/Mock_object) — это распространенный шаблон проектирования в объектно-ориентированном программировании. Произойдя от старого Французского слова 'mocquer' со значением 'высмеивать', оно эволюционировало до 'имитировать что-то реальное', что актуально, когда мы собираемся программировать. Пожалуйста, чтобы получать удовольствие от Ваших смарт-контрактов, если Вы хотите, то имитируете их всякий раз, когда можете. Это делает Вашу жизнь легче.
+
+## Юнит-тестирование контрактов с помощью мок-объектов {#unit-testing-contracts-with-mocks}
+
+По существу, мокинг контракта означает создание второй версии контракта, которая ведет себя очень похоже на оригинал, но такой, чтобы разработчик мог легче ее контролировать. Часто приходится иметь дело со сложными контрактами, в которых вы хотите [провести юнит-тестирование только небольших частей контракта](/developers/docs/smart-contracts/testing/). Проблема в том, что если для тестирования этой небольшой части требуется очень специфическое состояние контракта, которого трудно достичь?
+
+Можно каждый раз писать сложную логику настройки теста, которая приводит контракт в требуемое состояние, или же написать мок-объект. Имитировать контракт легко с помощью наследования. Просто создайте второй мок-контракт, который наследует от исходного. Теперь вы можете переопределить функции в своем мок-объекте. Давайте посмотрим это на примере.
+
+## Пример: закрытый ERC20 {#example-private-erc20}
+
+Мы используем пример контракта ERC-20, у которого есть начальный закрытый период. Владелец может управлять закрытыми пользователями, и только им будет разрешено получать токены вначале. По истечении определенного времени все смогут использовать токены. Если вам интересно, мы используем хук [`_beforeTokenTransfer`](https://docs.openzeppelin.com/contracts/5.x/extending-contracts#using-hooks) из новых контрактов OpenZeppelin v3.
+
+```solidity
+pragma solidity ^0.6.0;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+contract PrivateERC20 is ERC20, Ownable {
+ mapping (address => bool) public isPrivateUser;
+ uint256 private publicAfterTime;
+
+ constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {
+ publicAfterTime = now + privateERC20timeInSec;
+ }
+
+ function addUser(address user) external onlyOwner {
+ isPrivateUser[user] = true;
+ }
+
+ function isPublic() public view returns (bool) {
+ return now >= publicAfterTime;
+ }
+
+ function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
+ super._beforeTokenTransfer(from, to, amount);
+
+ require(_validRecipient(to), "PrivateERC20: invalid recipient");
+ }
+
+ function _validRecipient(address to) private view returns (bool) {
+ if (isPublic()) {
+ return true;
+ }
+
+ return isPrivateUser[to];
+ }
+}
+```
+
+А теперь давайте мокнем его.
+
+```solidity
+pragma solidity ^0.6.0;
+import "../PrivateERC20.sol";
+
+contract PrivateERC20Mock is PrivateERC20 {
+ bool isPublicConfig;
+
+ constructor() public PrivateERC20(0) {}
+
+ function setIsPublic(bool isPublic) external {
+ isPublicConfig = isPublic;
+ }
+
+ function isPublic() public view returns (bool) {
+ return isPublicConfig;
+ }
+}
+```
+
+Вы получите одно из следующих сообщений об ошибке:
+
+- `PrivateERC20Mock.sol: Ошибка типа: У переопределяющей функции отсутствует спецификатор "override".`
+- `PrivateERC20.sol: Ошибка типа: Попытка переопределить невиртуальную функцию.` `Вы забыли добавить "virtual"?`
+
+Поскольку мы используем новую версию Solidity 0.6, мы должны добавить ключевое слово `virtual` для функций, которые можно переопределить, и `override` для переопределяющей функции. Давайте добавим их в обе функции `isPublic`.
+
+Теперь в своих юнит-тестах вы можете использовать вместо этого `PrivateERC20Mock`. Когда вы хотите протестировать поведение во время закрытого периода использования, используйте `setIsPublic(false)` и аналогично `setIsPublic(true)` для тестирования открытого периода использования. Конечно, в нашем примере мы могли бы просто использовать [вспомогательные функции времени](https://docs.openzeppelin.com/test-helpers/0.5/api#increase), чтобы соответствующим образом изменить время. Но идея имитации теперь должна быть ясна, и вы можете представить сценарии, в которых это не так просто, как простое продвижение времени.
+
+## Имитация многих контрактов {#mocking-many-contracts}
+
+Может возникнуть беспорядок, если вам придется создавать еще один контракт для каждого отдельного мок-объекта. Если вас это беспокоит, вы можете взглянуть на библиотеку [MockContract](https://github.com/gnosis/mock-contract). Она позволяет переопределять и изменять поведение контрактов «на лету». Однако она работает только для имитации вызовов другого контракта, поэтому для нашего примера она не подойдет.
+
+## Имитация может быть еще более мощной {#mocking-can-be-even-more-powerful}
+
+Возможности имитации на этом не заканчиваются.
+
+- Добавление функций: полезно не только переопределять определенную функцию, но и просто добавлять дополнительные функции. Хорошим примером для токенов является наличие дополнительной функции `mint`, позволяющей любому пользователю бесплатно получать новые токены.
+- Использование в тестовых сетях: когда вы развертываете и тестируете свои контракты в тестовых сетях вместе со своим децентрализованным приложением, рассмотрите возможность использования мок-версии. Избегайте переопределения функций, если в этом нет крайней необходимости. В конце концов, вы хотите протестировать реальную логику. Но может быть полезно добавить, например, функцию сброса, которая просто сбрасывает состояние контракта к начальному, не требуя нового развертывания. Очевидно, что вы бы не хотели иметь это в контракте в основной сети.