> For the complete documentation index, see [llms.txt](https://docs.rome.builders/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rome.builders/ru/rukovodstva-dlya-razrabotchikov/dual-lane-app.md).

# Создать двухканальное приложение

Один **двухканальное приложение** — это один контракт Solidity, который и **MetaMask (EVM)** пользователь и **Phantom (Solana)** пользователь используют напрямую — один и тот же контракт, одно и то же состояние, каждый со своим кошельком, который у него уже есть. На этой странице подробно рассматривается *точно* что происходит на каждом шаге, с каждой стороны.

Пример — это небольшой **хранилище**: вы `вносите` USDC, а затем `выводите` его. «Stake / unstake», «supply / redeem», «tip / claim» имеют ту же структуру.

## Что вы пишете — стандартное хранилище ERC-20

В Rome USDC пользователя Solana на стороне EVM отображается как обычный **токен ERC-20** — SPL-обёртка для этого mint (например, `wUSDC`). Поэтому ваш контракт — обычное хранилище токенов: он забирает токены через `transferFrom` и возвращает их через `transfer`. Никакой специфики Rome:

```solidity
interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

contract Vault {
    IERC20 public immutable token;                 // обёртка wUSDC
    mapping(address => uint256) public balanceOf;
    constructor(IERC20 _token) { token = _token; }

    function deposit(uint256 amount) external {
        require(token.transferFrom(msg.sender, address(this), amount));
        balanceOf[msg.sender] += amount;
    }
    function withdraw(uint256 amount) external {
        balanceOf[msg.sender] -= amount;
        require(token.transfer(msg.sender, amount));
    }
}
```

> **Почему ERC-20, а не `payable` / `msg.value`?** Расходуемый баланс пользователя Solana — это **SPL-токен-аккаунт его кошелька**, отображаемый один к одному через эту ERC-20-обёртку — *не* нативный баланс EVM. Поэтому пользователь Solana всегда может профинансировать `transferFrom`внесение `payable` потребует нативную стоимость, которой у него нет в состоянии покоя. Стройте вокруг токена, и оба канала будут работать одинаково.

Разверните его с помощью Foundry или Hardhat, указав в конструкторе адрес обёртки для вашего токена (из [реестра](https://github.com/rome-protocol/rome-registry)). В Rome газовый токен — USDC, поэтому для развёртывания нужен небольшой баланс USDC на газ.

## Два канала

| Канал      | Кошелёк               | Как приложение вызывает это                       |
| ---------- | --------------------- | ------------------------------------------------- |
| **EVM**    | MetaMask (ключ EVM)   | `submitRomeTx` — стандартная операция записи Rome |
| **Solana** | Phantom (ключ Solana) | `submitRomeTxSolanaLane` — ключ EVM не нужен      |

Канал EVM — обычный. Остальная часть этой страницы посвящена **каналу Solana** — интересной половине.

## Ключевая идея: синтетический адрес — это транзитный узел

EVM-идентичность пользователя Solana — это его **синтетический адрес** — `keccak256(solana_pubkey)[12:]`. Это его `msg.sender` в контракте, но **он ничего не хранит в состоянии покоя.** Деньги пользователя находятся в его **кошельке Solana** (как SPL USDC), а на стороне EVM этот же баланс — это то, что `wUSDC.balanceOf(synthetic)` читается. Средства проходят *через* синтетический адрес:

* **Вход** (депозит): токен-аккаунт кошелька → токен-аккаунт синтетического адреса → контракт (через `transferFrom`).
* **Выход** (вывод): контракт → токен-аккаунт синтетического адреса → токен-аккаунт кошелька.

После каждого полного цикла синтетический адрес сводится к нулю.

## Один раз: Активировать (подготовить синтетический адрес)

У совершенно нового синтетического адреса ончейн-аккаунт не существует, пока вы его не создадите. Когда пользователь Solana действует впервые, его синтетический адрес **подготавливается** с помощью `create_pda` вызова — после этого вызовы, перемещающие средства (ERC-20 `transferFrom`, свип) могут подписываться им.

`submitRomeTxSolanaLane` делает это **автоматически при первом использовании** (`autoProvision` включён по умолчанию). Если вы предпочитаете показывать явный экран «Активировать» (одноразовая настройка аккаунта), сделайте это сами:

```javascript
import { provisionSynthetic, isSyntheticProvisioned } from "@rome-protocol/sdk";

const deps = { connection, proxyUrl, programId, chainId, payer: wallet.publicKey, signTransaction: wallet.signTransaction };
if (!(await isSyntheticProvisioned(connection, programId, synthetic))) {
  await provisionSynthetic(deps); // один create_pda; затем отправляйте записи с autoProvision: false
}
```

## Что нужно каждой стороне

|                                   | Нужно                                | Почему                                                                         |
| --------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------ |
| **Пользователь Solana (Phantom)** | **SOL** (немного)                    | оплачивает комиссию за транзакцию Solana для каждой транзакции канала          |
|                                   | **USDC как SPL-токен** в их кошельке | стоимость, которую они вносят (на стороне EVM отображается как `wUSDC`)        |
| **Пользователь EVM (MetaMask)**   | **USDC** как их баланс газа Rome     | газ + стоимость; они пополняют его, **мостя USDC** в Rome (фaucet отсутствует) |
| **Вы (разработчик)**              | кошелёк с **USDC** газом в Rome      | для развёртывания контракта                                                    |

## Входящая стоимость — депозит (шаг за шагом)

У пользователя Solana в кошельке Phantom есть SOL и USDC. Каждая транзакция канала **подписывается Phantom** — кошелёк подписывает её и отправляет в Solana RPC (proxy используется только для обнаружения аккаунтов):

1. **Лег фонда** — `buildFundLeg(...)` → `submitSolanaInstructions(...)`. Создаёт токен-аккаунт USDC синтетического адреса (если нужно) и выполняет **`ActivateAta`**, перемещая `amount` USDC из токен-аккаунта кошелька **в**синтетический адрес. Теперь `wUSDC.balanceOf(synthetic)` показывает этот баланс.
2. **Approve** — `submitRomeTxSolanaLane({ to: wUSDC, data: approve(vault, amount) })`. Позволяет хранилищу забрать токены. *(Обычно это первый вызов канала, поэтому здесь синтетический адрес автоматически подготавливается.)*
3. **Депозит** — `submitRomeTxSolanaLane({ to: vault, data: deposit(amount) })`. Хранилище выполняет `transferFrom(synthetic, vault, amount)` — USDC перемещается из синтетического адреса в хранилище и зачисляется на адрес синтетического адреса.

**Итоговый эффект:** USDC прошёл **кошелёк Phantom → (синтетический адрес) → хранилище.**

```javascript
import { syntheticAddress, buildFundLeg, submitSolanaInstructions, submitRomeTxSolanaLane } from "@rome-protocol/sdk";
import { encodeFunctionData, erc20Abi } from "viem";

const synthetic = syntheticAddress(wallet.publicKey);
const deps = { connection, proxyUrl, programId, chainId, payer: wallet.publicKey, signTransaction: wallet.signTransaction };

// 1) leg фонда — USDC кошелька → токен-аккаунт синтетического адреса (Phantom подписывает)
await submitSolanaInstructions(
  buildFundLeg({ programId, chainId, mint: usdcMint, amount: depositAmount, wallet: wallet.publicKey, synthetic }),
  { connection, feePayer: wallet.publicKey, signTransaction: wallet.signTransaction },
);

// 2) одобрить хранилище (первый вызов канала → синтетический адрес автоматически подготавливается)
await submitRomeTxSolanaLane(deps, { to: wUSDC, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [vault, depositAmount] }) });

// 3) депозит — хранилище забирает через transferFrom
await submitRomeTxSolanaLane(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "deposit", args: [depositAmount] }) });
```

## Исходящая стоимость — вывод (шаг за шагом)

Теперь пользователь выводит средства. Тоже с подписью Phantom:

1. **Вывод** — `submitRomeTxSolanaLane({ to: vault, data: withdraw(amount) })`. `vault.withdraw` выполняет `transfer(synthetic, amount)` — USDC перемещается из хранилища обратно в **токен-аккаунт синтетического адреса** .
2. **Лег свипа** — `buildSweepLeg(...)` даёт вам `HelperProgram.transfer_spl` вызов + аккаунты; выполните его (при необходимости создайте токен-аккаунт кошелька, затем `DoTxUnsigned` в Helper precompile), чтобы переместить USDC из синтетического адреса **обратно в собственный кошелёк пользователя Solana**. Синтетический адрес сводится к нулю.

**Итоговый эффект:** USDC прошёл **хранилище → (синтетический адрес) → кошелёк Phantom пользователя.** Ничто не остаётся без движения.

```javascript
import { buildSweepLeg } from "@rome-protocol/sdk";

// 1) вывод — хранилище возвращает USDC в токен-аккаунт синтетического адреса
await submitRomeTxSolanaLane(deps, { to: vault, data: encodeFunctionData({ abi, functionName: "withdraw", args: [amount] }) });

// 2) leg свипа — токен-аккаунт синтетического адреса → токен-аккаунт собственного кошелька пользователя
const sweep = buildSweepLeg({ programId, mint: usdcMint, amount, wallet: wallet.publicKey, synthetic });
await submitSolanaInstructions([sweep.ensureWalletAtaIx], { connection, feePayer: wallet.publicKey, signTransaction: wallet.signTransaction });
await submitRomeTxSolanaLane(deps, { to: sweep.helperTo, data: sweep.calldata, extraAccounts: sweep.extraAccounts });
```

## Подводные камни — всё это обрабатывает SDK

Это то, что самодельная транзакция канала Solana обычно делает неправильно; `submitRomeTxSolanaLane` SDK делает это за вас:

* **Подготовка.** Аккаунт свежего синтетического адреса должен быть создан (`create_pda`) перед любым вызовом, перемещающим средства, иначе он не сможет подписать перевод. Автоматически при первом использовании; можно отключить с помощью `autoProvision: false` + `provisionSynthetic`.
* **Расходуйте обёртку, а не `msg.value`.** Баланс пользователя Solana — это его SPL-токен-аккаунт, отображаемый как ERC-20-обёртка — перемещайте его с помощью `transfer` / `transferFrom`, никогда не нативную стоимость.
* **ComputeBudget.** EVM Rome требует повышенного лимита CU (\~1,35 млн) и большого frame heap (\~250 КБ). Дефолты Solana в 200K CU / 32 КБ приводят к ошибке.
* **Казначейский кошелёк.** Во время исполнения выплачивается небольшая комиссия на казначейский аккаунт по каждой цепочке; при обнаружении аккаунтов он пропускается, поэтому SDK добавляет его.
* **Куда отправляется.** Кошелёк **подписывает транзакцию Solana и отправляет её в Solana RPC** — не в proxy. Proxy используется только для обнаружения аккаунтов. On-chain программа выводит `msg.sender` из подписи Solana.
* **Газ — это USDC.** Фaucet отсутствует — заведите USDC через мост (см. [Получение финансирования](/ru/resursy/faucets.md)).

## То же приложение из MetaMask

Пользователь EVM вызывает тот же самый контракт с `submitRomeTx` — стандартные инструменты EVM, газ в USDC. Они по-прежнему `одобряют` затем `вносите` (ERC-20 как обычно), без leg'ов фонда/свипа (их токены уже находятся на их EVM-адресе). Оба пользователя используют одно и то же состояние `balanceOf` .

## Что дальше

* [Вызов EVM из Solana](/ru/rukovodstva-dlya-razrabotchikov/call-evm-from-solana.md) — механика канала Solana подробно
* [Вызов Solana из EVM](/ru/rukovodstva-dlya-razrabotchikov/call-solana-from-evm.md) — обратное направление (CPI)
* [Получение финансирования](/ru/resursy/faucets.md) — USDC как газовый токен; заведите его через мост


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.rome.builders/ru/rukovodstva-dlya-razrabotchikov/dual-lane-app.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
