For the complete documentation index, see llms.txt. This page is also available as Markdown.

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

Пошаговое руководство по двухканальному приложению — один контракт Solidity, используемый как пользователем MetaMask, так и пользователем Phantom (Solana), с точным описанием того, что происходит на каждом шаге.

Один двухканальное приложение — это один контракт 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:

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, указав в конструкторе адрес обёртки для вашего токена (из реестра). В 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 включён по умолчанию). Если вы предпочитаете показывать явный экран «Активировать» (одноразовая настройка аккаунта), сделайте это сами:

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

Нужно
Почему

Пользователь 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. ApprovesubmitRomeTxSolanaLane({ to: wUSDC, data: approve(vault, amount) }). Позволяет хранилищу забрать токены. (Обычно это первый вызов канала, поэтому здесь синтетический адрес автоматически подготавливается.)

  3. ДепозитsubmitRomeTxSolanaLane({ to: vault, data: deposit(amount) }). Хранилище выполняет transferFrom(synthetic, vault, amount) — USDC перемещается из синтетического адреса в хранилище и зачисляется на адрес синтетического адреса.

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

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

Теперь пользователь выводит средства. Тоже с подписью 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 пользователя. Ничто не остаётся без движения.

Подводные камни — всё это обрабатывает 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 через мост (см. Получение финансирования).

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

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

Что дальше

Последнее обновление

Это было полезно?