> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tenderly.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Предпросмотр транзакции в MetaMask Snap

> Добавьте предпросмотр транзакций в MetaMask через Snap, используя Tenderly Simulation API, чтобы пользователи могли увидеть результаты до подписания.

В этом руководстве показано, как добавить опцию «предпросмотр транзакции» в кошелёк с помощью [Tenderly Simulation API](/api-reference). Мы покажем, как добавить эту опцию в кошелёк MetaMask.

Функция «[предпросмотр транзакции](/simulations/transaction-preview)» позволяет пользователям увидеть точный результат их транзакций до отправки в продакшн-сеть. Кошельки, оснащённые этой функцией, упрощают пользователям понимание финансовых последствий их транзакций перед принятием обязательств.

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

Полный демонстрационный репозиторий можно найти на GitHub:

<Card title="Tenderly Simulate Asset Changes Snap" href="" />

<iframe src="https://youtube.com/embed/DgfTifOM_V0" style={{ aspectRatio: 16 / 9, width: '100%' }} />

Демонстрация Tenderly MetaMask Snap

<Warning>
  Это руководство предназначено только для демонстрационных целей, а именно для иллюстрации того, как добавить
  опцию предпросмотра транзакции в MetaMask Snap. Оно не предназначено и не рекомендуется для любого другого
  использования.
</Warning>

## Обзор проекта

Этот проект показывает, как интегрировать симуляции транзакций в кошелёк MetaMask. Мы будем работать с [MetaMask Snaps](https://metamask.io/snaps/).

Вы узнаете, как подключаться к [Tenderly Simulation API](/api-reference), отправлять данные транзакции, получать результаты симуляции и отображать их пользователю через UI MetaMask. Сюда входят изменения активов в долларах, изменения баланса нативных активов, выходное значение, изменения хранилища, логи событий и трейсы вызовов.

Симулированные транзакции затем можно открыть в Tenderly для дальнейшей отладки и тестирования.

Ниже приведён список функций, которые мы будем добавлять в MetaMask Snap. Похожие функции можно также добавить в любой кошелёк, чтобы помочь пользователям обрести уверенность при отправке транзакций.

<br />

| Функция                                        | Описание                                                                                       |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| **Отладка симуляции**                          | Автоматически сгенерированная ссылка для инспекции и отладки симуляции в Tenderly.             |
| **Публично доступные симуляции**               | Возможность включать/отключать публичный доступ к симуляциям.                                  |
| **Изменения активов в долларовом эквиваленте** | Данные в понятном формате, включая изменения активов, автоматически преобразованные в доллары. |
| **Изменения баланса нативных активов**         | Возможность отслеживать изменения баланса нативных активов во время выполнения контракта.      |
| **Выходное значение**                          | Увидеть результат вызова контракта, отображающий то, что контракт должен вернуть.              |
| **Изменения хранилища**                        | Увидеть любые изменения хранилища контракта во время выполнения.                               |
| **Логи событий**                               | Увидеть события, которые были сгенерированы во время выполнения контракта.                     |
| **Трейсы вызовов**                             | Разбивка всех вызовов функций, произошедших во время выполнения.                               |

### Предварительные требования

* Установленное расширение браузера [MetaMask Flask](https://chromewebstore.google.com/detail/metamask-flask-developmen/ljfoeinjpaedjfecbmggjgodbgkmjkjk)
* [Node.js](https://nodejs.org) (версии 16 или новее)
* [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) (версии 3)

<Note>
  Перед началом сборки MetaMask Snap обязательно отключите «реальную» версию MetaMask
  и [установите плагин MetaMask Flask
  Development](https://chromewebstore.google.com/detail/metamask-flask-developmen/ljfoeinjpaedjfecbmggjgodbgkmjkjk).
</Note>

Начнём сборку! 👷‍♂️

## Шаг 1. Настройка проекта MetaMask Snap

Установите официальный [Create Snap CLI](https://docs.metamask.io/snaps/get-started/quickstart) от MetaMask и создайте новый проект.

```bash title="example" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
yarn create @metamask/snap project-name

OR

npm create @metamask/snap project-name
```

Далее нам нужно запустить проект Snap и обслуживать фронтенд на [https://localhost:8000](https://localhost:8000/).

Из корня только что созданного проекта установите зависимости проекта с помощью Yarn.

```bash title="example" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
yarn
```

Запустите сервер разработки.

```bash title="example" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
yarn start
```

### Установка правильных разрешений

После установки Snap найдите файл `/packages/snap/snap.manifest.json` и установите разрешения, как показано ниже. Узнайте о разрешениях MetaMask [здесь](https://docs.metamask.io/snaps/reference/permissions/).

```json title="example.json" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
{
  // ...
  "initialPermissions": {
    "snap_dialog": {},
    "endowment:rpc": {
      "dapps": true,
      "snaps": false
    },
    "endowment:transaction-insight": {
      "allowTransactionOrigin": true
    },
    "endowment:network-access": {},
    "snap_manageState": {},
    "endowment:ethereum-provider": {
      "dapps": true,
      "snaps": true
    }
  }
}
```

Когда вы запустите приложение и попытаетесь подключить кошелёк, появится запрос на разрешение.

Нажмите кнопки **Connect** и **Approve & install**, чтобы продолжить.

<Frame caption="Install MetaMask Snap Permissions">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(6).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=fe280e95a931009bdc123e0018703f04" alt="Install MetaMask Snap Permissions" width="1920" height="1080" data-path="images/image (6).webp" />
</Frame>

## Шаг 2. Генерация токенов доступа Tenderly API

Опция предпросмотра транзакции в MetaMask Snap работает на базе Tenderly Simulation API.

Прежде чем начать использовать API, вам нужно сгенерировать токены доступа. Выполните следующие шаги:

1. [Войдите в Tenderly или создайте бесплатный аккаунт здесь](https://dashboard.tenderly.co/register).
2. Перейдите на [страницу Authorization](https://dashboard.tenderly.co/account/authorization) и нажмите кнопку **Generate Access Token**.

Если вам нужна помощь с аутентификацией API, следуйте этому руководству:

<Card title="Как сгенерировать токены доступа API" href="" />

<Frame caption="Generate Access Token from Authorization page">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(1).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=605d334b40293766a111dd1651f30d92" alt="Generate Access Token from Authorization page" width="1920" height="1080" data-path="images/image (1).webp" />
</Frame>

## Шаг 3. Написание логики для Snap

Основная логика, обеспечивающая работу Snap, хранится в трёх файлах:

* **Credentials Access** (`credentials-access.ts`)**:** управляет аутентификацией и доступом к Tenderly API.
* **Simulation** (`simulation.ts`)**:** основная логика взаимодействия с Tenderly API для симуляции транзакций и получения результатов симуляции.
* **Formatter** (`formatter.ts`)**:** логика для парсинга результатов симуляции и форматирования их в удобном виде в UI MetaMask.

### Credentials Access

Все методы, отвечающие за запрос, обновление и получение учётных данных Tenderly API, можно хранить в файле **credentials-access.ts**. [См. исходный код здесь](https://github.com/Tenderly/tenderly-metamask-snap-simulate-asset-changes/blob/main/packages/snap/src/tenderly/credentials-access.ts).

Наиболее важные методы:

* **`fetchCredentials()`**: эта функция получает учётные данные проекта Tenderly. Если учётные данные не сохранены, она запускает `handleUpdateTenderlyCredentials` для запроса новых.
* **`handleUpdateTenderlyCredentials()`**: эта функция обрабатывает процесс обновления учётных данных проекта Tenderly. Она получает новые учётные данные, вызывая `requestNewTenderlyCredentials`, а затем сохраняет их с помощью метода `snap_manageState` с операцией `'update'`.
* **`requestNewTenderlyCredentials()`**: эта функция запрашивает новые учётные данные Tenderly. Она вызывает `requestCredentials`, чтобы получить сырые данные учётных записей и проверить их правильность перед возвратом объекта с `accountId`, `projectId` и `accessToken`.

<Note>
  **snap\_manageState** — встроенный метод, позволяющий Snap сохранять до 100 МБ данных
  на диск. Узнайте больше [здесь](https://docs.metamask.io/snaps/reference/snaps-api/#snap_managestate).
</Note>

### Simulation

Далее нам нужно создать логику, которая отправляет данные транзакции в [Tenderly Simulation API](/api-reference), симулирует их и получает результаты.

Для этого мы будем использовать эндпоинт API **`simulate`**.

Эндпоинт: `https://api.tenderly.co/api/v1/account/{accountId}/project/{projectId}/simulate`

Чтобы всё было организованно, поместим логику симуляции в файл **simulation.ts**. [См. исходный код здесь.](https://github.com/Tenderly/tenderly-metamask-snap-simulate-asset-changes/blob/main/packages/snap/src/tenderly/simulation.ts)

Чтобы отправить данные транзакции в Tenderly Simulation API, нам нужно создать два ключевых метода:

* **simulate()**: это основная функция, которая обрабатывает симуляцию транзакции. Она получает учётные данные доступа к API, отправляет данные транзакции в Tenderly Simulation API и обрабатывает любые ошибки, возвращаемые API.

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
export async function simulate(
  transaction: { [key: string]: Json },
  transactionOrigin: string,
): Promise<Panel> {
  const credentials = await fetchCredentials(transactionOrigin);

  if (!credentials) {
    return panel([text('🚨 Tenderly access token updated. Please try again.')]);
  }

  const simulationResponse = await submitSimulation(transaction, credentials);
  const err = catchError(simulationResponse, credentials);

  return err || formatResponse(simulationResponse, credentials);
}
```

* **submitSimulation()**: эта функция отправляет запрос в API вместе с данными транзакции для симуляции.

Когда транзакция симулируется, мы можем сделать её публично доступной, вызвав API-эндпоинт share. Возможность поделиться симуляцией позволяет копировать ссылку на транзакцию и отправлять её кому угодно. Расшаренные транзакции можно просматривать без учётной записи Tenderly.

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
async function submitSimulation(
  transaction: { [key: string]: Json },
  credentials: TenderlyCredentials,
) {
  const chainId = await ethereum.request({ method: 'eth_chainId' });
  const response = await fetch(
    `https://api.tenderly.co/api/v1/account/${credentials.accountId}/project/${credentials.projectId}/simulate`,
    {
      method: 'POST',
      body: JSON.stringify({
        from: transaction.from,
        to: transaction.to,
        input: transaction.data,
        gas: hex2int(transaction.gas),
        value: hex2int(transaction.value),
        network_id: hex2int(chainId as string),
        save: true,
        save_if_fails: true,
        simulation_type: 'full',
        generate_access_list: false,
        source: 'tenderly-metamask-snap',
      }),
      headers: {
        'Content-Type': 'application/json',
        'X-Access-Key': credentials.accessToken,
      },
    },
  );

  const parsedResponse = await response.json();

  // Make the simulation publicly accessible
  if (parsedResponse?.simulation?.id) {
    await fetch(
      `https://api.tenderly.co/api/v1/account/${credentials.accountId}/project/${credentials.projectId}/simulations/${parsedResponse.simulation.id}/share`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Access-Key': credentials.accessToken,
        },
      },
    );
  }

  return parsedResponse;
}
```

### Formatter

После того как API вернёт результаты симуляции, нам нужно распарсить эти данные и отформатировать их в удобном для пользователя виде для отображения в UI MetaMask.

Эту логику можно хранить в файле **formatter.ts**. [См. исходный код здесь.](https://github.com/Tenderly/tenderly-metamask-snap-simulate-asset-changes/blob/main/packages/snap/src/tenderly/formatter.ts)

Основные методы форматирования:

* **`formatResponse()`**: эта функция получает сырые данные и учётные данные симуляции проекта Tenderly, вызывает индивидуальные функции форматирования для каждого релевантного раздела (например, изменения балансов, выходное значение, изменения активов и т. д.) и возвращает панель со всеми отформатированными выводами.
* **`formatBalanceDiff()`**: эта функция генерирует панель, показывающую изменения балансов для каждого аккаунта, участвующего в транзакции.
* **`formatOutputValue()`**: эта функция создаёт панель, представляющую выходные значения транзакции, если таковые имеются. Также она декодирует вывод, если это возможно.
* **`formatAssetChanges()`**: эта функция форматирует панель для отображения любых изменений активов, различая изменения ERC20, ERC721 и другие.
* **`formatStorageChanges()`**: эта функция создаёт панель, в которой перечислены любые изменения хранилища, произошедшие во время транзакции. Она уникально форматирует адреса и вложенные структуры данных для наглядности.
* **`formatEventLogs()`**: эта функция представляет логи событий, если они существуют, для каждой транзакции. Логи отформатированы для удобства чтения и включают входные значения.
* **`formatCallTrace()`**: эта функция создаёт отформатированную визуальную иерархию трейсов вызовов, рекурсивно показывая вложенные вызовы.
* **`formatSimulationUrl()`**: эта функция возвращает ссылку на полные детали симуляции в Tenderly Dashboard и отдельную ссылку для расшаривания.

## Шаг 4. Отображение данных симуляции в UI MetaMask

[Монорепозиторий MetaMask Snap](https://github.com/MetaMask/template-snap-monorepo) предоставляет набор предопределённых UI-элементов и конфигураций макета. Мы будем использовать эти элементы для расширения существующего UI MetaMask и отображения результатов симуляции.

Когда вы устанавливаете наш пример для MetaMask, мы показываем, как запускать разные типы симуляций:

* **Симуляция успешной транзакции** с предопределённым payload (успешный трансфер ERC-20 и трансфер NFT)
* **Симуляция неудачной транзакции** с предопределённым payload
* **Симуляция пользовательской транзакции** с любым payload

<Frame caption="Tenderly Snap UI">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(1)%20(1).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=c6f3c33067b72e0ac097fac8095498b0" alt="Tenderly Snap UI" width="1000" height="625" data-path="images/image (1) (1).webp" />
</Frame>

Для целей этого руководства мы покажем, как запускать симуляции с предопределённым payload, а также как добавить свой собственный payload и выполнить симуляцию.

### Успешная симуляция с предопределённым payload

Чтобы инициировать симуляцию, мы напишем функцию, которая будет отправлять предопределённые данные транзакции с нашего адреса кошелька и вызывать RPC-метод `eth_sendTransaction`.

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
// packages/site/src/utils/snap.ts

export const sendTransaction = async (data: any): Promise<any> => {
  try {
    const [from] = (await window.ethereum.request({
      method: 'eth_requestAccounts',
    })) as string[];

    if (!from) {
      return Promise.reject(Error('Failed to get an account'));
    }

    // https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction
    return window.ethereum.request({
      method: 'eth_sendTransaction',
      params: [
        {
          ...data,
          from,
        },
      ],
    });
  } catch (e) {
    console.error(e);
    return e;
  }
};
```

Вызов метода `eth_sendTransaction` запустит обработчик `onTransaction`, который вызовет нашу пользовательскую функцию `simulate`, определённую в `simulation.ts`.

```tsx title="example.tsx" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
// packages/snap/src/index.ts

export const onTransaction: OnTransactionHandler = async ({ transaction, transactionOrigin }) => {
  if (!isObject(transaction) || !hasProperty(transaction, 'to')) {
    return {
      content: {
        value: 'Unknown transaction type',
        type: NodeType.Text,
      },
    };
  }

  const simulationResponse = await simulate(transaction, transactionOrigin || '');

  return {
    content: {
      children: simulationResponse.children,
      type: NodeType.Panel,
    },
  };
};
```

В обработчике `onTransaction` функция `simulate` вызывается с транзакцией и её источником в качестве аргументов.

Функция `simulate` отвечает за получение учётных данных доступа, отправку данных транзакции в Tenderly API, обработку любых ошибок и форматирование ответа API для вывода.

Результаты симуляции отображаются в UI MetaMask. На изображениях ниже показан успешный трансфер токена ERC20 в 1 USDC на `demo.eth`. Вывод генерируется функциями, определёнными в файле `formatter.ts`.

<Frame caption="ERC20 Transfer - send 1 USDC to demo.eth">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(2).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=bb92ed11d3534aae5065bb162b2d2a83" alt="ERC20 Transfer - send 1 USDC to demo.eth" width="1252" height="842" data-path="images/image (2).webp" />
</Frame>

Например, блок **Asset Changes**, показывающий изменения в долларовом эквиваленте в результате симуляции, генерируется функцией `formatAssetChanges()`.

Вы также получите ссылку на симулированную транзакцию, которую можно открыть в Tenderly и исследовать её далее в [Debugger](/debugger/overview) либо ресимулировать с другими значениями.

Эта ссылка генерируется функцией `formatSimulationUrl()`, определённой в файле `formatter.ts`.

```typescript title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
export function formatSimulationUrl(data: any, credentials: TenderlyCredentials): Component[] {
  const simulationUrl = `https://dashboard.tenderly.co/${credentials.accountId}/${credentials.projectId}/simulator/${data.simulation?.id}`;
  const sharedSimulationUrl = `https://tdly.co/shared/simulation/${data.simulation?.id}`;

  return [
    heading('Tenderly Dashboard:'),
    text('See full simulation details in Tenderly.'),
    text(`**Status:** ${data.transaction?.status ? 'Success ✅' : 'Failed ❌'}`),
    copyable(`${simulationUrl}`),
    text('Share simulation details with others! 🤗'),
    copyable(`${sharedSimulationUrl}`),
  ];
}
```

### Неудачная транзакция с предопределённым payload

Во втором примере мы симулируем неудачную транзакцию с предопределённым payload.

На изображении ниже показан неудачный трансфер токена ERC20 в 1 000 000 USDC на `demo.eth`.

<Frame caption="Preview of the failed transaction">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(3).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=aba83c7ebdcd5583f17f53c9b21fe306" alt="Preview of the failed transaction" width="1920" height="1080" data-path="images/image (3).webp" />
</Frame>

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

Пользователь может продолжить отладку неудачной транзакции и ресимулировать её для проверки различных решений.

### Симуляция с пользовательским payload

Чтобы запустить симуляцию с пользовательским payload, вам нужно добавить объект, соответствующий [спецификации транзакций Ethereum](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction).

<Frame caption="Custom payload UI">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(4).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=3910b1ef37fc7a16edfc5718e7446df5" alt="Custom payload UI" width="1920" height="1080" data-path="images/image (4).webp" />
</Frame>

Он может содержать следующие поля:

* `from`: `DATA`, 20 байт — адрес, с которого отправляется транзакция.
* `to`: `DATA`, 20 байт — (опционально при создании нового контракта) адрес, на который направляется транзакция.
* `gas`: `QUANTITY` — (опционально, по умолчанию: 90000) целое число газа, предоставленного для выполнения транзакции. Неиспользованный газ будет возвращён.
* `gasPrice`: `QUANTITY` — (опционально, по умолчанию: To-Be-Determined) целое число `gasPrice`, используемого для каждого оплаченного газа.
* `value`: `QUANTITY` — (опционально) целое число значения, отправляемого с этой транзакцией.
* `data`: `DATA` — скомпилированный код контракта ИЛИ хэш вызываемой сигнатуры метода и закодированные параметры.
* `nonce`: `QUANTITY` — (опционально) целое число nonce. Это позволяет перезаписывать ваши собственные ожидающие транзакции, использующие тот же nonce.

Все числовые значения должны быть шестнадцатеричными строками, а адреса — адресами Ethereum (20 байт).

<Frame caption="Send a custom payload">
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/image%20(5).webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=6544e62caa22bfd1c5affec7c1f52792" alt="Send a custom payload" width="1920" height="1080" data-path="images/image (5).webp" />
</Frame>

После ввода пользовательского payload пользователи могут инициировать симуляцию с помощью той же функции `sendTransaction`, что и раньше. Это передаёт пользовательский payload в обработчик `onTransaction`, который затем запускает функцию `simulate` для выполнения симуляции.

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

В этом руководстве вы узнали, как добавить опцию предпросмотра транзакции в MetaMask Snap на базе Tenderly Simulation API. [Исходный код этого проекта на GitHub](https://github.com/Tenderly/tenderly-metamask-snap-simulate-asset-changes).

Симуляция транзакций на Tenderly может быть инициирована тремя способами, в зависимости от вашего проекта и потребностей. Узнайте, как интегрировать симуляции в свои dapp'ы, с помощью этих справочных ресурсов:

<Card title="Simulation UI" href="" />

<Card title="Simulation API" href="" />

<Card title="Simulation RPC" href="" />
