> 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/klyuchevye-ponyatiya/token-interop.md).

# Совместимость токенов

Как токены ERC-20 и SPL представляют один и тот же базовый аккаунт в Rome.

Rome представляет токен ERC-20 и лежащий в его основе токен SPL как одну общую учётную запись. На этой странице объясняется, как токены работают в EVM и Solana.

## Модель общего состояния

Для обычного кошелька токен ERC-20 в Rome является **прозрачной обёрткой** над учётной записью токена SPL в Solana. Баланс ERC-20 кошелька равен его балансу SPL. Учётной записью токена владеет адрес, производный от адреса EVM-программы. Это его **PDA внешнего полномочия**.

<figure><img src="https://1351373662-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F671PKS8ZQSNqtoC8PGvz%2Fuploads%2Fgit-blob-65af9700f322d308e2cb17da3d107f87b51201a7%2Ftoken-interop.svg?alt=media" alt="An EVM address and its Solana token account, owned by its external-authority PDA. Multiple wrappers read the same token account. The wrapper escrow account and Solana pool vaults hold tokens of the same Solana mint."><figcaption></figcaption></figure>

* Без задержки моста: баланс ERC-20 кошелька равен его балансу SPL.
* Обычные кошельки, включая кошельки MetaMask и синтетические адреса, не имеют фрагментации ликвидности. Каждая обёртка над минтом показывает один и тот же баланс кошелька. Метод `balanceOf` считывает этот баланс из учётной записи токена кошелька. Токены, хранимые внутри контрактов EVM, хранятся отдельно для каждой обёртки (см. [Контракт-обёртка](#the-wrapper-contract)).
* Для токенов кошелька ничего не блокируется с одной стороны и не выпускается с другой. Токены, хранимые контрактом, находятся в собственной эскроу-учётной записи токенов обёртки в Solana. Каждая обёртка отслеживает эти токены отдельно.

**Почему вторая обёртка не разделяет баланс**

Две обёртки над одним и тем же минтом показывают одинаковый баланс кошелька. Обёртки считывают `totalSupply` из минта Solana. В Hadrian (тестовой сети) три обёртки wUSDC возвращали совпадающие балансы кошельков. Все три обёртки wUSDC (9.0.0, 8.0.0 и May 2026) возвращали объём выпуска минта Solana.

Каждая фабрика допускает одну обёртку на минт. Новая версия фабрики может развернуть другую обёртку для этого минта. Реестр указывает каноническую обёртку для каждой цепочки.

Кошелёк Solana, непосредственно хранящий USDC, и адрес EVM владеют токенами одного и того же минта. Пулы Solana используют одни и те же хранилища и резервы для обоих.

Таблица держателей в [Через обозреватель](/ru/prilozheniya-v-rome/via.md) формируется из событий `Transfer` каждой обёртки. Она может отличаться от баланса учётной записи токенов, поскольку прямые переводы SPL, `ActivateAta`, а также переводы через другую обёртку не отображаются в событиях этой обёртки.

> Пути импорта ниже используют `@rome-protocol/rome-solidity`. Пакета нет в npm; установите его из публичного [`rome-solidity`](https://github.com/rome-protocol/rome-solidity) репозитория с помощью `npm install github:rome-protocol/rome-solidity`, или `forge install rome-protocol/rome-solidity` с переназначением; контрактам обёртки и фабрики также требуется OpenZeppelin в Foundry (см. [Развертывание Solidity-контрактов](/ru/rukovodstva-dlya-razrabotchikov/deploy-solidity.md#using-the-rome-solidity-sdk)). Интерфейсы прекомпилированных контрактов находятся в [`contracts/interface.sol`](https://github.com/rome-protocol/rome-solidity/blob/master/contracts/interface.sol).

## Контракт-обёртка

`SPL_ERC20` (и его вариант с кэшированным треком `SPL_ERC20_cached`, который фабрика развёртывает для каждого минта, кроме минта с активированным хуком перевода; см. [минты Token-2022](#token-2022-mints)) предоставляют полный интерфейс ERC-20 поверх минта SPL:

* `balanceOf()` — считывает баланс ATA пользователя из Solana (для адреса контракта считывает собственный реестр обёртки, см. ниже)
* `transfer()` / `transferFrom()` — перемещает токены в Solana
* `approve()` / `allowance()` — разрешения ERC-20 для каждого получателя, хранимые в хранилище EVM (учётная запись токенов SPL не имеет разрешений в стиле EVM). Они отделены от одноразового предоставления прав ниже: чтобы вообще перемещать токены пользователя, обёртка действует как SPL-делегат этого пользователя, а пользователь предоставляет ей это право один раз для каждого кошелька
* `totalSupply()` — считывает объём выпуска минта SPL

Контракт не может отправить одноразовое предоставление прав, поэтому токены контракта хранятся в собственной учётной записи токенов обёртки и отслеживаются в реестре обёртки. `balanceOf(contract)` считывает этот реестр.

## Фабрика

`ERC20SPLFactory` развёртывает обёртку для любого минта SPL:

```solidity
import {ERC20SPLFactory} from "@rome-protocol/rome-solidity/contracts/erc20spl/erc20spl_factory.sol";

// Развернуть обёртку, загрузив имя/символ из метаданных Token-2022 минта или Metaplex
address wrapper = factory.add_spl_token_with_metadata(splMint);

// Или указать имя/символ вручную
address wrapper = factory.add_spl_token_no_metadata(splMint, "USD Coin", "USDC");
```

Адрес действующей фабрики из `активен` записей реестра (v0.2.0): Hadrian `0x37c13e4ff21fb914eb24014dcbd0f8dbeaa31078`. Более ранние фабрики всё ещё находятся в цепочке, но помечены как `устаревшие` или `выведенные из эксплуатации` в [реестре](https://github.com/rome-protocol/rome-registry/tree/main/chains); не используйте их. Реестр является источником истины, поэтому считывайте действующий адрес из него (см. [Чтение списка контрактов](/ru/spravochnik/contract-addresses.md#reading-the-contracts-list)) вместо копирования его в свой код.

## Канонические минты

В цепочке нет контракта реестра токенов. Канонические обёртки и токены газа/моста курируются во внешнем [`rome-protocol/registry`](https://github.com/rome-protocol/rome-registry); не требующие разрешений обёртки, созданные через `add_spl_token_no_metadata` обнаруживаются по событию в цепочке `TokenCreated` . Реестр указывает каноническую обёртку для каждого минта в каждой цепочке. Балансы кошельков являются общими для обёрток. Каждая обёртка отдельно отслеживает балансы, хранимые контрактами EVM.

## Операции SPL из Solidity

Для примитивов SPL, подписываемых пользовательским PDA, используйте **HelperProgram** прекомпилированный контракт (`0xFF…09`) — создание ATA, переводы SPL и конвертацию газа↔lamports:

```solidity
import {IHelperProgram} from "@rome-protocol/rome-solidity/contracts/interface.sol";

IHelperProgram helper = IHelperProgram(0xFF00000000000000000000000000000000000009);

helper.create_ata(user, mint);              // создать ATA пользователя для минта
helper.transfer_spl(to, tokens, mint);      // перевести SPL из PDA вызывающей стороны
```

`transfer_spl` имеет несколько перегрузок (включая вариант с делегатом для `transferFrom` потоков); см. `interface.sol` для точных сигнатур. В кэшированном треке эквивалентные операции находятся по адресам `ISplCached` (`0xFF…05`и `IAssociatedSplCached` (`0xFF…06`). Контракт последовательно использует один трек.

## Разовое разрешение для каждого кошелька

Обёртка перемещает токены пользователя как **SPL-делегат** учётной записи токенов этого пользователя. Поэтому кошелёк должен предоставить ей это право один раз для каждой обёртки, прежде чем обёртка сможет что-либо перемещать от его имени. Предоставление выполняется одним вызовом прекомпилированного контракта Helper (`0xff00000000000000000000000000000000000009`):

```solidity
helper.approve_spl(wrapper, type(uint64).max, mint);   // approve_spl(wrapper, 2^64-1, mint)
```

`approve_spl` подписывается собственной учётной записью пользователя, поэтому его отправляет пользователь, а не обёртка или ваш контракт. Минт `mint_id()` находится в обёртке.

* **Кому это нужно.** Каждому кошельку EVM и синтетическому адресу каждого пользователя Solana — для каждой обёртки, токены которой он хочет перемещать. Контракт, который получает токены с помощью `transferFrom` работает только для пользователей, предоставивших права обёртке.
* **Как проверить.** Вызовите `isEnabled(user)` на обёртке. Он возвращает `true` после предоставления прав.
* **Как это выглядит при отсутствии прав.** Перевод откатывается с ошибкой SPL `0x4` (несоответствие владельца).
* **Один делегат на учётную запись токенов.** Учётная запись токенов SPL имеет одного делегата. Предоставление прав другой программе на той же учётной записи токенов, например мосту или маршрутизатору, заменяет права обёртки, и обёртка перестаёт работать для этого пользователя, пока он не предоставит их снова. Проверяйте `isEnabled` перед переводом вместо предположения, что ранее предоставленные права всё ещё действуют.

Когда новая версия фабрики заменяет обёртку, каждый кошелёк предоставляет новой обёртке разрешение с помощью `approve_spl`. Старая обёртка может перемещать токены этого кошелька, только пока остаётся делегатом учётной записи токенов. Предоставление прав новой обёртке заменяет этого делегата.

Названия [руководство по приложению с двумя линиями](/ru/rukovodstva-dlya-razrabotchikov/dual-lane-app.md#one-time-grant-the-wrapper) содержит полный код для обеих линий.

## Внесение и вывод

* **В EVM** — сторона SPL зачисляет средства на ATA пользователя, принадлежащий PDA; обёртка ERC-20 немедленно отражает баланс. Входящие межсетевые переводы завершаются без доверия через авторизацию на мосту, подписанную пользователем.
* **Конвертировать газ в токен газа** — вызовите `Withdraw` прекомпилированный контракт (`0x42…16`). `withdraw_to_ata(wei)` сжигает нативный газ и зачисляет эквивалентную сумму в собственных десятичных разрядах токена газа на учётную запись токенов вызывающей стороны; затем обёртка токена газа (например, wUSDC) показывает её как баланс. `withdraw_to_pda(wei)` выплачивает lamports на PDA вызывающей стороны и применяется только к цепочке, где газом является SOL; он откатывается в Hadrian и Rubicon, использующих USDC в качестве газа, поэтому используйте `withdraw_to_ata` там. Сумма указывается в единицах газа с 18 десятичными разрядами и должна быть целым числом единиц токена газа: в цепочке с USDC в качестве газа — кратным 10^12 (одна единица с 6 десятичными разрядами), иначе вызов откатывается. `HelperProgram.deposit_from_ata(wei)` работает в обратную сторону и превращает токен газа на учётной записи токенов вызывающей стороны обратно в нативный газ, с тем же правилом кратности. Это конвертации токена газа, а не вывод других токенов.
* **В Solana, любой другой токен** — собственный баланс обёртки уже является балансом SPL, поэтому его вывод выполняется переводом SPL:
  * `HelperProgram.transfer_spl_to_signer(amount, mint)` отправляет токены вызывающей стороны на учётную запись токенов кошелька Solana, подписавшего транзакцию. Этот обратный маршрут достигает собственного кошелька пользователя только в линии Solana, подписываемой кошельком. При вызове из MetaMask или через ретранслятор подписывающей стороной является прокси-плательщик Rome, поэтому вызов завершается ошибкой либо токены поступают Rome. Используйте `transfer_spl` или `RomeBridgeWithdraw.bridgeOutToSolana` вместо этого.
  * `RomeBridgeWithdraw.bridgeOutToSolana(solanaRecipient, amount, mint)` отправляет их на любой кошелёк Solana. Для этого требуется предоставление пользователем прав контракту моста (`approve_spl(bridge, …)`, см. примечание об одном делегате выше), а учётная запись токенов получателя должна существовать; `ensureRecipientAta(solanaRecipient, mint)` создаёт её.
  * `RomeBridgeWithdraw` также сжигает USDC для другой цепочки через CCTP (`burnUSDC`) и отправляет другие активы через Wormhole (`burnETH`, `burnToWormhole`).

Возьмите актуальный `RomeBridgeWithdraw` адрес из реестра.

## Токен газа

Каждая цепочка имеет собственный токен газа: SOL или токен SPL. Источником цен Rome Proxy может быть поток API Hermes, пул Meteora DAMM v1, пул Meteora DAMM v2 или фиксированная цена. Hadrian (тестовая сеть) и Rubicon (основная сеть) используют USDC. Hadrian использует тестовый USDC. Rubicon использует реальный USDC. Универсального токена газа по умолчанию нет.

## минты Token-2022

Фабрика также оборачивает минты Token-2022. Она запрашивает у прекомпилированного контракта Helper сведения о минте с помощью `mint_info(mint)` (селектор `0xe24bf5d4`) и выбирает обёртку на основе ответа. `mint_info` возвращает пять значений:

| Значение           | Значение                                                                                 |
| ------------------ | ---------------------------------------------------------------------------------------- |
| `tokenProgram`     | программа токенов, владеющая минтом (классическая программа SPL Token или Token-2022)    |
| `десятичные знаки` | число десятичных разрядов минта                                                          |
| `hookProgram`      | программа хука перевода либо ноль, если хука нет или хук присутствует, но не активирован |
| `feeBps`           | комиссия за перевод в базисных пунктах либо ноль, если её нет или она установлена в ноль |
| `extensions`       | битовая карта расширений Token-2022, имеющихся у минта: бит N для типа расширения N      |

Что получает минт:

| Минт                                                                                           | Обёртка                                                                                                    | `wrapper_kind_by_mint` |
| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------- |
| Обычный минт, минт Token-2022 без хука либо минт, хук которого присутствует, но не активирован | кэшированную обёртку (`SPL_ERC20_cached`)                                                                  | `1`                    |
| Минт Token-2022 с активированным хуком перевода                                                | обёртку с хуком (`SPL_ERC20_Token2022Hooked`), развёрнутую через метод фабрики `hooked_wrapper_deployer()` | `2`                    |

Минт с комиссией за перевод получает кэшированную обёртку, а его событие `Transfer` сообщает сумму, поступившую после комиссии, а не отправленную сумму. Тип обёртки, полученный минтом, можно считать из фабрики с помощью `wrapper_kind_by_mint(mint)`; на действующей фабрике Hadrian обёртки USDC, ETH и SOL все возвращают `1`.

Что отклоняется:

* Минт с активированным хуком перевода не может получить одну из обычных обёрток. Развёртывание `SPL_ERC20` или `SPL_ERC20_cached` на нём напрямую откатывается с `ArmedTransferHookUnsupported(mint, hookProgram)`. Через фабрику этот случай вместо этого направляется к обёртке с хуком.
* Адрес, не являющийся минтом SPL, заставляет `mint_info` откатываться с `Недопустимая учётная запись минта SPL: <address>`.
* Минт, уже имеющий обёртку, откатывается с `Токен существует`, а уже используемый символ вызывает откат с `Токен с таким символом существует`.
* `add_spl_token_with_metadata` сначала считывает имя и символ из собственных метаданных Token-2022 минта, а затем из Metaplex. Если у минта нет ни того, ни другого, происходит откат с `Минт утверждает, что имени нет: используйте add_spl_token_no_metadata`.

Как ведёт себя обёртка с хуком:

* Token-2022 выполняет хук при каждом переводе, и хуку требуются учётные записи, зависящие от перевода. Обёртка не может определить их самостоятельно, поэтому вызывающая сторона разрешает полный список учётных записей из списка extra-account-metas минта и передаёт его. Обёртка проверяет фиксированную часть этого списка (программу хука и её учётную запись валидации) и передаёт его без изменений.
* Обычный `transfer`, `transferFrom` и `bridgeOutToSolana` откатываться с `HookAccountPlanRequired`. Используйте `transferWithHookAccounts`, `transferFromWithHookAccounts` и `bridgeOutToSolanaWithHookAccounts`, которые принимают список учётных записей как дополнительный аргумент.
* Если программа хука минта изменяется после развёртывания обёртки, переводы откатываются с `HookConfigurationChanged`.
* Она перемещает токены прямым межпрограммным вызовом, в устаревшем треке, и не использует кэшированный трек. Контракт последовательно использует один трек, а вызовы в устаревшем треке выполняются только в атомарном режиме, поэтому вызов, использующий обёртку с хуком, должен помещаться в одну атомарную транзакцию.
* Она не ведёт реестр балансов контрактов. Токены, отправленные контракту, поступают на собственную учётную запись токенов контракта, и контракт может отправить их дальше только после того, как предоставит `approve_spl` права самой обёртке.
* Ей требуется то же [одноразовое предоставление прав](#one-time-grant-per-wallet) что и любой другой обёртке.

## Ограничения

* Суммы токенов SPL имеют тип `uint64` (максимум 18 446 744 073 709 551 615)
* Разрешения ERC-20 (`approve` / `allowance`) хранятся в EVM. Обёртка перемещает токены как SPL-делегат пользователя, права которому пользователь предоставляет один раз для каждой обёртки (см. [Разовое разрешение для каждого кошелька](#one-time-grant-per-wallet))
* Символы обёрток ERC-20 должны быть уникальны в пределах фабрики

## Что дальше

* [справочнике Contract Addresses](/ru/spravochnik/contract-addresses.md) — прекомпилированные контракты и адреса для каждой цепочки
* [Вызов Solana из EVM](/ru/rukovodstva-dlya-razrabotchikov/call-solana-from-evm.md) — CPI и операции SPL из Solidity
* [Как Solana и EVM встречаются в Rome](broken://pages/6d5752686c28351d88f0c43af7db8dfdbfa07851)
* [Интеграция программы Solana](/ru/rukovodstva-dlya-razrabotchikov/integrate-solana-program.md)


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.rome.builders/ru/klyuchevye-ponyatiya/token-interop.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
