[English](https://hb3-accelerator.com/gitea/VC-HB3-Accelerator/Docs/src/branch/main/docs.en/back-docs/MULTICHAIN_GOVERNANCE_TOKEN_TRANSFER.md) | **Русский**

# Мультичейн-управление переводом токенов DLE

## Обзор системы

Система мультичейн-управления DLE позволяет холдерам токенов создавать предложения по переводу токенов со своего кошелька на другой адрес (или в казну) через процесс голосования во всех сетях, где развернут контракт DLE. Каждая сеть имеет независимый кворум, но предложения координируются и отображаются как единое целое.

## Архитектура

### Мультичейн-контракты DLE
- Один DLE может быть развернут в нескольких блокчейн-сетях (например, Sepolia, Arbitrum Sepolia, Base Sepolia)
- Каждый контракт DLE в каждой сети работает независимо
- Предложения создаются, голосуются и выполняются в каждой сети отдельно
- ID предложений уникальны для каждой сети (предложение с ID=1 в Sepolia и предложение с ID=1 в Arbitrum Sepolia - это разные предложения)

### Группировка предложений
- Предложения с одинаковым описанием и инициатором группируются в одну карточку
- Карточка отображает статус предложения во всех сетях DLE
- Каждая сеть в карточке имеет свой собственный ID предложения, состояние и результаты голосования

## Процесс перевода токенов

### Этап 1: Создание предложения

#### Описание процесса
1. Пользователь заполняет форму перевода токенов:
   - **Описание предложения** - текстовое описание цели перевода
   - **Адрес получателя** - адрес кошелька или казны, на который будут переведены токены
   - **Количество токенов** - количество DLE токенов для перевода
   - **Длительность голосования** - период времени, в течение которого можно голосовать
   - **Ваш подключенный кошелек** - автоматически заполняется адресом подключенного кошелька (токены будут отправлены с этого адреса)

2. Система определяет все сети, где развернут контракт DLE

3. **Последовательное создание предложений в каждой сети:**
   - Для каждой сети DLE:
     - Переключение MetaMask на соответствующую сеть
     - Задержка 1 секунда после переключения
     - Создание предложения в контракте DLE этой сети
     - Получение уникального ID предложения для этой сети
     - Задержка 3 секунды после подтверждения транзакции (5 секунд для Base Sepolia)
   - При ошибках RPC выполняется автоматический retry с экспоненциальной задержкой (до 3 попыток)

4. **Подписи в MetaMask:**
   - Пользователь должен подписать транзакцию создания предложения в каждой сети DLE
   - Количество подписей = количество сетей DLE
   - Каждая подпись создает отдельное предложение в соответствующей сети

#### Технические детали
- **Функция контракта:** `createProposal(description, duration, operation, targetChains, timelockDelay)`
  - **Порядок параметров:** `description`, `duration`, `operation`, `targetChains` (массив), `timelockDelay`
  - **targetChains:** Массив ID сетей, где будет выполнена операция (обычно `[chainId]` для текущей сети)
- **Операция:** `_transferTokens(sender, recipient, amount)` - где `sender` = адрес инициатора предложения
  - **Сигнатура:** `_transferTokens(address,address,uint256)` - **все три параметра обязательны!**
  - `sender` получается автоматически из `signer.getAddress()` при создании предложения
- **ID предложения:** Генерируется автоматически контрактом в каждой сети (начинается с 0, инкрементируется)
- **Группировка:** Предложения с одинаковым `description` и `initiator` группируются в одну карточку

#### Кодирование операции

Операция для выполнения должна быть закодирована в формате ABI (Application Binary Interface) перед передачей в `createProposal`.

**Для операции перевода токенов `_transferTokens(address,address,uint256)`:**
1. **Сигнатура функции:** `_transferTokens(address,address,uint256)`
2. **Селектор функции:** Первые 4 байта от `keccak256(signature)`
3. **Параметры:**
   - `sender` - адрес отправителя (инициатора предложения)
   - `recipient` - адрес получателя токенов
   - `amount` - количество токенов (в wei, т.е. количество * 10^18)

**Пример кодирования (JavaScript/ethers.js):**
```javascript
// Способ 1: Использование Interface (рекомендуется)
const functionSignature = '_transferTokens(address,address,uint256)';
const iface = new ethers.Interface([`function ${functionSignature}`]);
const encodedOperation = iface.encodeFunctionData('_transferTokens', [
  senderAddress,      // адрес инициатора
  recipientAddress,   // адрес получателя
  ethers.parseUnits(amount.toString(), 18) // количество в wei
]);

// Способ 2: Ручное кодирование
const functionSignature = '_transferTokens(address,address,uint256)';
const selectorBytes = ethers.keccak256(ethers.toUtf8Bytes(functionSignature));
const selector = '0x' + selectorBytes.slice(2, 10); // первые 4 байта

const abiCoder = ethers.AbiCoder.defaultAbiCoder();
const encodedParams = abiCoder.encode(
  ['address', 'address', 'uint256'],
  [senderAddress, recipientAddress, ethers.parseUnits(amount.toString(), 18)]
);

const encodedOperation = ethers.concat([selector, encodedParams]);
```

**Важные моменты:**
- `sender` должен совпадать с адресом инициатора предложения (проверяется в контракте при выполнении)
- `amount` **ОБЯЗАТЕЛЬНО** передается в wei (1 токен = 10^18 wei) - используйте `ethers.parseUnits(amount.toString(), 18)`
- **КРИТИЧЕСКИ ВАЖНО:** `sender` должен определяться из `signer.getAddress()` при создании предложения в каждой сети отдельно, а не один раз до цикла
- Операция кодируется для каждой сети отдельно с актуальным адресом signer для этой сети
- Контракт декодирует операцию при выполнении и проверяет соответствие `sender` и `initiator`

**КРИТИЧЕСКИ ВАЖНО - Правильная реализация:**
```javascript
// ✅ ПРАВИЛЬНО: Кодирование внутри цикла с актуальным адресом signer для каждой сети
async function createProposalsInAllChains(allChains, formData) {
  const results = [];
  
  for (let index = 0; index < allChains.length; index++) {
    const chainId = allChains[index];
    
    // 1. Переключаемся на нужную сеть
    await switchToVotingNetwork(chainId.toString());
    await new Promise(resolve => setTimeout(resolve, 1000)); // Задержка после переключения
    
    // 2. КРИТИЧЕСКИ ВАЖНО: Получаем адрес signer для текущей сети
    const provider = new ethers.BrowserProvider(window.ethereum);
    const signer = await provider.getSigner();
    const senderAddress = await signer.getAddress(); // Адрес инициатора из signer!
    
    // 3. Кодируем операцию с актуальным адресом signer для этой сети
    const transferCallData = encodeTransferTokensCall(
      senderAddress,        // адрес инициатора из signer (обязательно!)
      formData.recipient,   // адрес получателя
      formData.amount       // количество (будет сконвертировано в wei)
    );
    
    // 4. Создаем предложение
    const proposalData = {
      description: formData.description,
      duration: formData.duration,
      operation: transferCallData,
      targetChains: [chainId],
      timelockDelay: 0
    };
    
    await createProposal(contractAddress, proposalData);
  }
}

function encodeTransferTokensCall(sender, recipient, amount) {
  const functionSignature = '_transferTokens(address,address,uint256)';
  const iface = new ethers.Interface([`function ${functionSignature}`]);
  
  // КРИТИЧЕСКИ ВАЖНО: конвертируем amount в wei
  const amountInWei = ethers.parseUnits(amount.toString(), 18);
  
  const encodedCall = iface.encodeFunctionData('_transferTokens', [
    sender,        // адрес инициатора (обязательно! должен быть из signer.getAddress())
    recipient,     // адрес получателя
    amountInWei   // количество в wei (обязательно!)
  ]);
  
  return encodedCall;
}

// ❌ НЕПРАВИЛЬНО: Кодирование один раз до цикла
// const transferCallData = encodeTransferTokensCall(formData.sender, ...); // НЕПРАВИЛЬНО!
// for (const chainId of allChains) {
//   await createProposal(contractAddress, { operation: transferCallData, ... }); // sender может не совпасть!
// }

// ❌ НЕПРАВИЛЬНО: Отсутствует sender или amount не в wei
// const transferFunctionSelector = ethers.id("_transferTokens(address,uint256)"); // НЕПРАВИЛЬНО!
// const amount = transferData.amount; // НЕПРАВИЛЬНО! Нужна конвертация в wei
```

**Другие поддерживаемые операции:**
- `_addModule(bytes32,address)` - добавление модуля
- `_removeModule(bytes32)` - удаление модуля
- `_addSupportedChain(uint256)` - добавление поддерживаемой сети
- `_removeSupportedChain(uint256)` - удаление поддерживаемой сети
- `_updateVotingDurations(uint256,uint256)` - обновление времени голосования
- `_updateQuorumPercentage(uint256)` - обновление процента кворума
- `_updateDLEInfo(...)` - обновление информации DLE
- `_setLogoURI(string)` - обновление URI логотипа

#### Результат
- Создано N предложений (по одному в каждой сети DLE)
- Каждое предложение имеет уникальный ID в своей сети
- Все предложения отображаются как одна карточка в интерфейсе
- Карточка показывает статус предложения в каждой сети

---

### Этап 2: Голосование

#### Описание процесса
1. Пользователь видит карточку предложения с информацией о всех сетях DLE

2. Пользователь выбирает голос "За" или "Против"

3. **Последовательное голосование во всех активных сетях:**
   - Система определяет все активные цепочки (state === 0 или 'active', не выполнены, не отменены)
   - Для каждой активной сети:
     - Переключение MetaMask на соответствующую сеть
     - Задержка 1 секунда после переключения
     - **Проверка баланса токенов в этой сети** (балансы могут отличаться в разных сетях)
       - Если баланс отсутствует, сеть пропускается с предупреждением
       - Голосование продолжается в других сетях
     - Голосование с использованием **уникального ID предложения для этой сети**
     - Задержка 3 секунды после подтверждения транзакции (5 секунд для Base Sepolia)

4. **Подписи в MetaMask:**
   - Пользователь должен подписать транзакцию голосования в каждой активной сети DLE
   - Количество подписей = количество активных сетей DLE
   - Каждая подпись регистрирует голос в соответствующей сети

#### Технические детали
- **Функция контракта:** `vote(proposalId, support)` - где `proposalId` уникален для каждой сети
- **Проверка ID:** Система использует `chain.id` (ID предложения из конкретной сети), а не общий ID группы
- **Проверка баланса:** Баланс токенов проверяется **в каждой сети отдельно** перед голосованием
  - В мультичейн-системе балансы могут отличаться в разных сетях
  - Если в сети нет токенов, голосование в этой сети пропускается
  - Контракт также проверяет баланс через `getPastVotes()` и вернет ошибку, если токенов нет
- **Вес голоса:** Зависит от баланса токенов голосующего в соответствующей сети
- **Независимые кворумы:** Каждая сеть имеет свой собственный кворум

#### Результат
- Голос зарегистрирован во всех активных сетях DLE
- Каждая сеть обновляет свои счетчики голосов (forVotes, againstVotes)
- Карточка предложения обновляется с новыми данными голосования

---

### Этап 3: Выполнение предложения

#### Условия выполнения
**КРИТИЧЕСКИ ВАЖНО:** Предложение может быть выполнено только при условии, что кворум достигнут **во всех сетях DLE**, где предложение активно.

Условия для каждой сети:
- Состояние предложения: `ReadyForExecution` (state === 5)
- Кворум достигнут: `forVotes >= quorumRequired`
- Большинство голосов "За": `forVotes > againstVotes`
- Предложение не выполнено: `executed === false`
- Предложение не отменено: `canceled === false`
- Истек период голосования (если применимо)
- Истек период timelock (если применимо)

#### Описание процесса
1. Система проверяет, что предложение готово к выполнению во всех активных сетях DLE

2. **Последовательное выполнение во всех готовых сетях:**
   - Для каждой сети, где предложение готово к выполнению:
     - Переключение MetaMask на соответствующую сеть
     - Задержка 1 секунда после переключения
     - Выполнение предложения с использованием **уникального ID предложения для этой сети**
     - Задержка 3 секунды после подтверждения транзакции (5 секунд для Base Sepolia)

3. **Подписи в MetaMask:**
   - Пользователь должен подписать транзакцию выполнения в каждой готовой сети DLE
   - Количество подписей = количество готовых сетей DLE
   - Каждая подпись выполняет перевод токенов в соответствующей сети

#### Технические детали
- **Функция контракта:** `executeProposal(proposalId)` - где `proposalId` уникален для каждой сети
- **Операция перевода:** `_transferTokens(sender, recipient, amount)`
  - `sender` = адрес инициатора предложения (проверяется в контракте)
  - `recipient` = адрес получателя из предложения
  - `amount` = количество токенов из предложения
- **Проверка безопасности:** Контракт проверяет, что `sender` совпадает с `initiator` предложения
- **Перевод токенов:** Токены переводятся с кошелька инициатора, а не с баланса контракта

#### Результат
- Перевод токенов выполнен во всех готовых сетях DLE
- Каждая сеть независимо выполняет перевод с кошелька инициатора
- Карточка предложения обновляется, показывая статус "Выполнено" во всех сетях

---

## Отмена предложения

### Условия отмены
- Только инициатор предложения может отменить его
- Предложение должно быть активным (не выполнено, не отменено)
- Отмена возможна в любой момент до выполнения

### Процесс отмены
1. **Последовательная отмена во всех активных сетях:**
   - Для каждой активной сети:
     - Переключение MetaMask на соответствующую сеть
     - Задержка 1 секунда после переключения
     - Отмена предложения с использованием **уникального ID предложения для этой сети**
     - Задержка 3 секунды после подтверждения транзакции

2. **Подписи в MetaMask:**
   - Пользователь должен подписать транзакцию отмены в каждой активной сети DLE
   - Количество подписей = количество активных сетей DLE

### Технические детали
- **Функция контракта:** `cancelProposal(proposalId, reason)`
- **Проверка прав:** Контракт проверяет, что вызывающий является инициатором предложения

---

## Важные особенности системы

### 1. Уникальность ID предложений
- **Каждая сеть имеет свой собственный счетчик предложений**
- Предложение с ID=1 в Sepolia и предложение с ID=1 в Arbitrum Sepolia - это **разные предложения**
- При группировке система сохраняет ID из каждой сети отдельно
- При голосовании/выполнении/отмене используется правильный ID для каждой сети

### 2. Группировка предложений
- Предложения группируются по ключу: `${description}_${initiator}`
- Одна карточка = одно логическое предложение во всех сетях
- Карточка отображает:
  - Общее описание
  - Инициатора
  - Список сетей с их статусами
  - Результаты голосования по каждой сети
  - Общий статус (активно/выполнено/отменено)

### 3. Независимые кворумы
- **Каждая сеть имеет свой собственный кворум**
- Кворум рассчитывается на основе общего предложения токенов в соответствующей сети
- Голосование в одной сети не влияет на кворум в другой сети
- Для выполнения предложения кворум должен быть достигнут **во всех сетях**

### 4. Последовательное выполнение операций
- Все операции (создание, голосование, выполнение, отмена) выполняются **последовательно**, а не параллельно
- Это необходимо, так как MetaMask может работать только с одной сетью одновременно
- Между операциями есть задержки для стабилизации MetaMask
- **КРИТИЧЕСКИ ВАЖНО:** Использование `Promise.all` для параллельного выполнения недопустимо и приведет к ошибкам

### 5. Обработка ошибок
- **Retry для временных ошибок RPC:**
  - Автоматический retry до 3 попыток
  - Экспоненциальная задержка (2s, 4s, 8s)
  - Только для retryable ошибок (Internal JSON-RPC error, rate limiting)
- **Ошибки в отдельных сетях:**
  - Если операция не удалась в одной сети, процесс продолжается для других сетей
  - Пользователь получает сводку успешных и неудачных операций
- **Обработка отсутствия баланса:**
  - Перед голосованием в каждой сети проверяется баланс токенов
  - Если в сети нет токенов, голосование в этой сети пропускается с предупреждением
  - Голосование продолжается в других сетях, где баланс есть
  - Контракт также проверяет баланс и вернет ошибку `ErrNoPower`, если токенов нет

### 6. Безопасность
- **Проверка инициатора:** При выполнении контракт проверяет, что `sender` совпадает с `initiator`
- **Проверка баланса:** Перед голосованием проверяется наличие токенов **в каждой сети отдельно**
  - Балансы могут отличаться в разных сетях
  - Если в сети нет токенов, голосование в этой сети пропускается
  - Контракт также проверяет баланс через `getPastVotes()` и вернет ошибку `ErrNoPower`, если токенов нет
- **Проверка состояния:** Перед каждой операцией проверяется актуальное состояние предложения
- **Валидация данных:** Все данные предложения валидируются перед отправкой в контракт

### 7. Перевод токенов
- **Источник токенов:** Токены переводятся с кошелька инициатора предложения, а не с баланса контракта
- **Получатель:** Может быть любой адрес (кошелек или казна)
- **Количество:** Указывается инициатором при создании предложения
- **Проверка баланса:** Контракт проверяет достаточность баланса инициатора перед выполнением

---

## Пользовательский интерфейс

### Карточка предложения
- **Заголовок:** Описание предложения
- **Инициатор:** Адрес создателя предложения
- **Список сетей:**
  - Название сети
  - Статус (Активно/Выполнено/Отменено/Истекло)
  - ID предложения в этой сети
  - Результаты голосования (За/Против)
  - Кворум (достигнут/не достигнут)
- **Действия:**
  - Голосовать "За" / "Против" (если активно)
  - Выполнить (если готово к выполнению)
  - Отменить (если инициатор)

### Индикаторы статуса
- **Активно:** Предложение открыто для голосования
- **Готово к выполнению:** Кворум достигнут во всех сетях
- **Выполнено:** Перевод токенов выполнен во всех сетях
- **Отменено:** Предложение отменено инициатором
- **Истекло:** Истек период голосования

---

## Технические детали реализации

### Кодирование операций

Подробное описание процесса кодирования операций для создания предложений см. в разделе [Кодирование операции](#кодирование-операции) (Этап 1: Создание предложения).

### Структура данных предложения
```javascript
{
  id: number,                    // ID группы (из первой сети)
  description: string,           // Описание предложения
  initiator: address,            // Адрес инициатора
  deadline: number,              // Дедлайн голосования
  chains: [                      // Массив данных по каждой сети
    {
      id: number,                // УНИКАЛЬНЫЙ ID для этой сети
      chainId: number,            // ID сети (11155111, 421614, 84532)
      networkName: string,        // Название сети
      contractAddress: address,   // Адрес контракта DLE в этой сети
      state: number,              // Состояние (0=Active, 3=Executed, 4=Canceled, 5=ReadyForExecution)
      forVotes: bigint,          // Голоса "За"
      againstVotes: bigint,      // Голоса "Против"
      quorumRequired: bigint,    // Требуемый кворум
      executed: boolean,         // Выполнено
      canceled: boolean,          // Отменено
      transactionHash: string     // Хеш транзакции создания
    }
  ],
  createdAt: number,             // Время создания
  uniqueId: string              // Уникальный ключ группировки
}
```

### Функции контракта DLE

#### createProposal
```solidity
function createProposal(
    string memory _description,
    uint256 _duration,
    bytes memory _operation,
    uint256[] memory _targetChains,
    uint256 _timelockDelay
) public returns (uint256 proposalId)
```

#### vote
```solidity
function vote(uint256 _proposalId, bool _support) public
```

#### executeProposal
```solidity
function executeProposal(uint256 _proposalId) public
```

#### cancelProposal
```solidity
function cancelProposal(uint256 _proposalId, string memory _reason) public
```

#### _transferTokens (internal)
```solidity
function _transferTokens(
    address _sender,
    address _recipient,
    uint256 _amount
) internal
```

---

## Примеры использования

### Пример 0: Кодирование операции перевода токенов

Перед созданием предложения необходимо закодировать операцию. **КРИТИЧЕСКИ ВАЖНО:** Используйте правильную сигнатуру и конвертируйте amount в wei.

```javascript
import { ethers } from 'ethers';

// Получаем адрес инициатора из signer
const signer = await provider.getSigner();
const sender = await signer.getAddress(); // Адрес инициатора (обязательно!)

// Параметры перевода
const recipient = '0x1234567890123456789012345678901234567890'; // Получатель
const amount = 100; // 100 токенов (в обычных единицах, не в wei)

// Кодирование операции
const functionSignature = '_transferTokens(address,address,uint256)';
const iface = new ethers.Interface([`function ${functionSignature}`]);

// КРИТИЧЕСКИ ВАЖНО: конвертируем amount в wei
const amountInWei = ethers.parseUnits(amount.toString(), 18); // 100 * 10^18 wei

const encodedOperation = iface.encodeFunctionData('_transferTokens', [
  sender,        // адрес инициатора (обязательно!)
  recipient,     // адрес получателя
  amountInWei    // количество в wei (обязательно!)
]);

// encodedOperation теперь можно использовать в createProposal
// Результат: 0x... (селектор функции + закодированные параметры)

// Создание предложения с правильным порядком параметров
const tx = await dle.createProposal(
  "Перевод 100 токенов в казну",  // description
  86400,                          // duration (1 день в секундах)
  encodedOperation,               // operation
  [chainId],                      // targetChains (массив!)
  0                               // timelockDelay
);
```

**Результат:** Закодированная операция в формате bytes, готовая для передачи в `createProposal`.

**Частые ошибки:**
- ❌ Использование `_transferTokens(address,uint256)` - неправильная сигнатура
- ❌ Отсутствие параметра `sender` - контракт не сможет проверить инициатора
- ❌ Передача `amount` без конвертации в wei - неправильное количество токенов
- ❌ Неправильный порядок параметров в `createProposal` - ошибка вызова контракта

### Пример 1: Создание предложения в 3 сетях
1. Пользователь создает предложение "Перевод 100 токенов в казну"
2. Система определяет 3 сети: Sepolia, Arbitrum Sepolia, Base Sepolia
3. Создаются 3 предложения:
   - Sepolia: ID=5
   - Arbitrum Sepolia: ID=3
   - Base Sepolia: ID=7
4. Все 3 предложения отображаются как одна карточка

### Пример 2: Голосование
1. Пользователь голосует "За" за предложение
2. Система голосует в 3 сетях:
   - Sepolia: vote(5, true)
   - Arbitrum Sepolia: vote(3, true)
   - Base Sepolia: vote(7, true)
3. Каждое голосование требует отдельной подписи в MetaMask

### Пример 3: Выполнение
1. Кворум достигнут во всех 3 сетях
2. Система выполняет предложение в 3 сетях:
   - Sepolia: executeProposal(5)
   - Arbitrum Sepolia: executeProposal(3)
   - Base Sepolia: executeProposal(7)
3. В каждой сети токены переводятся с кошелька инициатора на адрес получателя

---

## Ограничения и особенности

### Ограничения
- MetaMask может работать только с одной сетью одновременно
- Операции выполняются последовательно, что может занять время при большом количестве сетей
- Ошибки RPC могут потребовать ручного retry

### Особенности
- Если предложение не создано в одной из сетей (из-за ошибки), оно все равно может быть создано в других сетях
- Голосование возможно только в тех сетях, где предложение активно
- Выполнение возможно только если кворум достигнут во всех активных сетях

---

## Безопасность

### Защита от атак
- Проверка инициатора при выполнении
- Проверка баланса перед переводом
- Независимые кворумы в каждой сети
- Валидация всех входных данных

### Рекомендации
- Всегда проверяйте адрес получателя перед созданием предложения
- Убедитесь, что у вас достаточно токенов для перевода
- Проверяйте статус предложения перед голосованием/выполнением
- Следите за транзакциями в каждой сети

---

## Известные проблемы и исправления

### Исправленные критические ошибки

#### 1. Отсутствие конвертации amount в wei
**Проблема:** Параметр `amount` передавался без конвертации в wei, что приводило к неправильному количеству токенов при выполнении.

**Исправление:** Добавлена обязательная конвертация через `ethers.parseUnits(amount.toString(), 18)` перед кодированием операции.

**Файл:** `frontend/src/views/smartcontracts/TransferTokensFormView.vue`

**Статус:** ✅ Исправлено

#### 2. Неправильная сигнатура функции _transferTokens
**Проблема:** Использовалась неправильная сигнатура `_transferTokens(address,uint256)` вместо `_transferTokens(address,address,uint256)`, что приводило к ошибкам декодирования в контракте.

**Исправление:** Исправлена сигнатура функции и добавлен обязательный параметр `sender` (адрес инициатора).

**Файл:** `frontend/src/utils/dle-contract.js` (функция `createTransferTokensProposal`)

**Статус:** ✅ Исправлено

#### 3. Неправильный порядок параметров в createProposal
**Проблема:** В функцию `createProposal` передавался `governanceChainId` вместо `targetChains` в 4-м параметре, что нарушало сигнатуру контракта.

**Исправление:** Исправлен порядок параметров согласно сигнатуре контракта: `description`, `duration`, `operation`, `targetChains`, `timelockDelay`.

**Файл:** `frontend/src/utils/dle-contract.js` (функция `createTransferTokensProposal`)

**Статус:** ✅ Исправлено

#### 4. Неправильное кодирование операции при создании предложений (КРИТИЧЕСКАЯ)
**Проблема:** Операция перевода токенов кодировалась один раз до цикла по сетям, используя адрес из `formData.value.sender`. По документации, `sender` должен определяться из `signer.getAddress()` при создании предложения в каждой сети, что гарантирует совпадение с инициатором предложения.

**Исправление:** 
- Кодирование операции перемещено внутрь цикла по сетям
- Добавлено получение адреса signer для каждой сети через `await signer.getAddress()`
- Добавлена проверка соответствия адреса signer адресу из формы
- Операция теперь кодируется с актуальным адресом signer для каждой сети отдельно

**Файл:** `frontend/src/views/smartcontracts/TransferTokensFormView.vue` (функция `submitForm`)

**Статус:** ✅ Исправлено (2025-01-XX)

#### 5. Параллельное выполнение в executeMultichainProposal (КРИТИЧЕСКАЯ)
**Проблема:** Функция `executeMultichainProposal` использовала `Promise.all` для параллельного выполнения операций во всех сетях одновременно. Это не работает с MetaMask, который может работать только с одной сетью одновременно.

**Исправление:**
- Заменен `Promise.all` на последовательный цикл `for`
- Добавлено переключение сетей через `switchToVotingNetwork` для каждой сети
- Добавлены задержки после переключения сетей (1 секунда) и после подтверждения транзакций (3 секунды, 5 секунд для Base Sepolia)
- Добавлена фильтрация только готовых к выполнению цепочек

**Файл:** `frontend/src/composables/useProposals.js` (функция `executeMultichainProposal`)

**Статус:** ✅ Исправлено (2025-01-XX)

#### 6. Отсутствие переключения сетей в voteOnMultichainProposal
**Проблема:** Функция `voteOnMultichainProposal` не использовала переключение сетей и проверку баланса токенов, что требовалось по документации для корректной работы в мультичейн-среде.

**Исправление:**
- Добавлено переключение сетей через `switchToVotingNetwork` для каждой сети
- Добавлена проверка баланса токенов перед голосованием в каждой сети через `checkTokenBalance`
- Добавлены задержки после переключения сетей (1 секунда) и после подтверждения транзакций (3 секунды, 5 секунд для Base Sepolia)
- Добавлена фильтрация только активных цепочек
- Добавлена обработка ошибок с пропуском сетей при отсутствии баланса

**Файл:** `frontend/src/composables/useProposals.js` (функция `voteOnMultichainProposal`)

**Статус:** ✅ Исправлено (2025-01-XX)

### Текущая корректная реализация

Все функции кодирования операций и мультичейн-операции теперь используют:
- ✅ Правильную сигнатуру: `_transferTokens(address,address,uint256)`
- ✅ Все три параметра: `sender`, `recipient`, `amount`
- ✅ Конвертацию amount в wei: `ethers.parseUnits(amount.toString(), 18)`
- ✅ Правильный порядок параметров в `createProposal`
- ✅ Определение `sender` из `signer.getAddress()` при создании предложения в каждой сети
- ✅ Последовательное выполнение операций во всех мультичейн-функциях (не параллельное)
- ✅ Переключение сетей перед каждой операцией в мультичейн-функциях
- ✅ Проверку баланса токенов перед голосованием в каждой сети

### Проверка корректности

Перед развертыванием убедитесь, что:
1. Функция `_transferTokens` кодируется с **тремя** параметрами (sender, recipient, amount)
2. `amount` всегда конвертируется в wei перед кодированием
3. `sender` получается из `signer.getAddress()` при создании предложения в каждой сети и совпадает с инициатором предложения
4. Порядок параметров в `createProposal` соответствует сигнатуре контракта
5. Все мультичейн-операции (создание, голосование, выполнение) используют последовательное выполнение с переключением сетей
6. При голосовании проверяется баланс токенов в каждой сети отдельно

---

## Заключение

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

**Важно:** Все критические ошибки в кодировании операций исправлены. Код соответствует документации и контракту.

