> 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/es/conceptos-basicos/token-interop.md).

# Interop de tokens

Cómo los tokens ERC-20 y SPL son la misma cuenta subyacente en Rome.

Rome representa un token ERC-20 y su token SPL subyacente como una sola cuenta compartida. Esta página explica cómo funcionan los tokens en EVM y Solana.

## El modelo de estado compartido

Rome no bloquea tokens de un lado y acuña copias envueltas en el otro. Un token ERC-20 en Rome es un **envoltorio transparente** sobre una cuenta de token SPL en Solana: el saldo ERC-20 *es* el saldo SPL.

<figure><img src="https://1082203750-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 ERC-20 wrapper over the same SPL token account on Solana"><figcaption></figcaption></figure>

* Sin demora de puente: el saldo ERC-20 es el saldo SPL
* Sin fragmentación de liquidez: DeFi en ambos lados ve los mismos tokens
* Sin riesgo de puente: no hay un escrow separado que explotar

> Las rutas de importación a continuación usan `@rome-protocol/rome-solidity`. El paquete no está en npm; instálalo desde el repositorio público [`rome-solidity`](https://github.com/rome-protocol/rome-solidity) con `npm install github:rome-protocol/rome-solidity`, o `forge install rome-protocol/rome-solidity` con una remapificación; los contratos wrapper y factory también necesitan OpenZeppelin bajo Foundry (ver [Deploy Solidity Contracts](/es/guias-para-desarrolladores/deploy-solidity.md#using-the-rome-solidity-sdk)). Las interfaces precompiladas están en [`contracts/interface.sol`](https://github.com/rome-protocol/rome-solidity/blob/master/contracts/interface.sol).

## El contrato wrapper

`SPL_ERC20` (y su variante de track en caché `SPL_ERC20_cached`, que la factory despliega para cada mint excepto uno con un transfer hook armado; ver [mints Token-2022](#token-2022-mints)) proporcionan una interfaz ERC-20 completa sobre un mint SPL:

* `balanceOf()` — lee el saldo ATA del usuario desde Solana (para una dirección de contrato lee el propio libro mayor del wrapper, ver abajo)
* `transfer()` / `transferFrom()` — mover tokens en Solana
* `approve()` / `allowance()` — asignaciones ERC-20 por gastador mantenidas en el almacenamiento EVM (una cuenta de token SPL no tiene asignaciones al estilo EVM). Son separadas de la concesión única de abajo: para mover los tokens de un usuario en absoluto, el wrapper actúa como delegado SPL de ese usuario, y el usuario le concede ese derecho una vez por wallet
* `totalSupply()` — lee el suministro del mint SPL

Un contrato no puede enviar la concesión única, por lo que los tokens de un contrato se mantienen en la propia cuenta de token del wrapper y se registran en el libro mayor del wrapper. `balanceOf(contract)` lee ese libro mayor.

## La factory

`ERC20SPLFactory` despliega un wrapper para cualquier mint SPL:

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

// Despliega un wrapper, cargando name/symbol desde los metadatos Token-2022 del mint o Metaplex
address wrapper = factory.add_spl_token_with_metadata(splMint);

// O especifica name/symbol manualmente
address wrapper = factory.add_spl_token_no_metadata(splMint, "USD Coin", "USDC");
```

Dirección activa de la factory, de las `live` entradas del registro (v0.2.0): Hadrian `0x37c13e4ff21fb914eb24014dcbd0f8dbeaa31078`. Las factories anteriores siguen en la cadena pero están marcadas como `obsoletas` o `retiradas` de la cadena en el [registro](https://github.com/rome-protocol/rome-registry/tree/main/chains); no las uses. El registro es la fuente de verdad, así que lee de él la dirección activa (ver [Leer la lista de contratos](/es/referencia/contract-addresses.md#reading-the-contracts-list)) en lugar de copiar una en tu código.

## Mints canónicos

No existe un contrato de registro de tokens en la cadena. Los wrappers canónicos y los tokens de gas/puente se curan en el [`rome-protocol/registry`](https://github.com/rome-protocol/rome-registry); los wrappers sin permisos creados mediante `add_spl_token_no_metadata` se descubren a partir del `evento TokenCreated` en la cadena. Esto mantiene cada activo mapeado a un único mint SPL canónico sin fragmentar la liquidez.

## Operaciones SPL desde Solidity

Para primitivas SPL firmadas por el usuario-PDA, usa el **HelperProgram** precompile (`0xFF…09`) — creación de ATA, transferencias SPL y conversión gas↔lamports:

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

IHelperProgram helper = IHelperProgram(0xFF00000000000000000000000000000000000009);

helper.create_ata(user, mint);              // crear el ATA del usuario para un mint
helper.transfer_spl(to, tokens, mint);      // transferir SPL desde la PDA del llamante
```

`transfer_spl` tiene varias sobrecargas (incluida una variante de delegado para `transferFrom` flujos); ver `interface.sol` para las firmas exactas. En la ruta en caché, las operaciones equivalentes viven en `ISplCached` (`0xFF…05`) y `IAssociatedSplCached` (`0xFF…06`). Un contrato usa una ruta de forma consistente.

## Concesión única por wallet

El wrapper mueve los tokens de un usuario como el **delegado SPL** de la cuenta de token de ese usuario. Por tanto, una wallet tiene que concederle ese derecho una vez por wrapper, antes de que el wrapper pueda mover nada por ella. La concesión es una llamada al precompile Helper (`0xff00000000000000000000000000000000000009`):

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

`approve_spl` está firmada por la propia cuenta del usuario, así que el usuario la envía, no el wrapper ni tu contrato. El mint es `mint_id()` en el wrapper.

* **Quién lo necesita.** Toda wallet EVM, y toda dirección sintética de un usuario de Solana, para cada wrapper cuyos tokens quiera mover. Un contrato que extrae tokens con `transferFrom` solo funciona para usuarios que han concedido al wrapper.
* **Cómo comprobarlo.** Llama a `isEnabled(user)` en el wrapper. Devuelve `true` una vez que la concesión está en vigor.
* **Cómo se ve cuando falta.** La transferencia revierte con el error SPL `0x4` (discordancia de propietario).
* **Un delegado por cuenta de token.** Una cuenta de token SPL tiene un solo delegado. Darle a otro programa una concesión sobre la misma cuenta de token, como un puente o un router, reemplaza la concesión del wrapper, y el wrapper deja de funcionar para ese usuario hasta que vuelva a concedérsela. Comprueba `isEnabled` antes de una transferencia en lugar de asumir que una concesión anterior sigue ahí.

La [guía de la app de doble carril](/es/guias-para-desarrolladores/dual-lane-app.md#one-time-grant-the-wrapper) tiene el código completo para ambos carriles.

## Depositar y retirar

* **Hacia EVM** — el lado SPL acredita la ATA del usuario, de la que es dueño su PDA; el wrapper ERC-20 refleja inmediatamente el saldo. Las transferencias entrantes cross-chain se liquidan sin confianza mediante una autorización firmada por el usuario en el puente.
* **Envuelve gas en el token de gas** — llama a `Withdraw` precompile (`0x42…16`). `withdraw_to_ata(wei)` quema gas nativo y acredita la cantidad equivalente, en los propios decimales del token de gas, en la cuenta de token del llamante, que luego el wrapper del token de gas (por ejemplo wUSDC) muestra como saldo. `withdraw_to_pda(wei)` paga lamports a la PDA del llamante y solo aplica a una cadena cuyo gas es SOL; revierte en Hadrian y Rubicon, que usan USDC como gas, así que usa `withdraw_to_ata` allí. La cantidad está en unidades de gas con 18 decimales y debe ser un número entero de unidades de token de gas: en una cadena cuyo gas es USDC, un múltiplo de 10^12 (una unidad de 6 decimales), de lo contrario la llamada revierte. `HelperProgram.deposit_from_ata(wei)` hace lo contrario y convierte el token de gas en la cuenta de token del llamante de vuelta en gas nativo, con la misma regla del múltiplo. Esto son conversiones de token de gas, no una salida para otros tokens.
* **Hacia Solana, cualquier otro token** — el saldo propio del wrapper ya es el saldo SPL, así que sacarlo es una transferencia SPL:
  * `HelperProgram.transfer_spl_to_signer(amount, mint)` envía los tokens del llamante a la cuenta de token de la wallet de Solana que firmó la transacción. Esta es la ruta de regreso para los usuarios de wallet Solana.
  * `RomeBridgeWithdraw.bridgeOutToSolana(solanaRecipient, amount, mint)` los envía a cualquier wallet de Solana. Necesita la concesión del usuario para el contrato del puente (`approve_spl(bridge, …)`, ver la nota de un delegado arriba), y la cuenta de token del destinatario debe existir; `ensureRecipientAta(solanaRecipient, mint)` la crea.
  * `RomeBridgeWithdraw` también quema USDC hacia otra cadena mediante CCTP (`burnUSDC`) y envía otros activos mediante Wormhole (`burnETH`, `burnToWormhole`).

Obtén la `RomeBridgeWithdraw` dirección activa del registro.

## Token de gas

Cada cadena tiene su propio token de gas — cualquier token SPL, valorado mediante un pool Meteora DAMM (v1 o v2, configurable). Las cadenas públicas (Rubicon y Hadrian) usan USDC; en Hadrian es USDC de prueba, en Rubicon USDC real. No existe un token de gas predeterminado universal.

## mints Token-2022

La factory también envuelve mints Token-2022. Le pregunta al precompile Helper cuál es el mint, con `mint_info(mint)` (selector `0xe24bf5d4`), y elige el wrapper a partir de la respuesta. `mint_info` devuelve cinco valores:

| Valor          | Significado                                                                                             |
| -------------- | ------------------------------------------------------------------------------------------------------- |
| `tokenProgram` | el programa de tokens que posee el mint (el programa clásico SPL Token o Token-2022)                    |
| `decimals`     | los decimales del mint                                                                                  |
| `hookProgram`  | el programa de transfer-hook, o cero cuando no hay hook o el hook está presente pero no armado          |
| `feeBps`       | la comisión de transferencia en puntos básicos, o cero cuando no hay ninguna o está establecida en cero |
| `extensiones`  | un bitmap de las extensiones de Token-2022 que lleva el mint, bit N para la extensión de tipo N         |

Qué recibe un mint:

| Mint                                                                                         | Wrapper                                                                                                | `wrapper_kind_by_mint` |
| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------- |
| Un mint ordinario, un mint Token-2022 sin hook, o uno cuyo hook está presente pero no armado | el wrapper en caché (`SPL_ERC20_cached`)                                                               | `1`                    |
| Un mint Token-2022 con un transfer hook armado                                               | el wrapper con hook (`SPL_ERC20_Token2022Hooked`), desplegado a través del `hooked_wrapper_deployer()` | `2`                    |

Un mint con comisión de transferencia obtiene el wrapper en caché, y su evento `Transfer` informa la cantidad que llegó después de la comisión, no la cantidad enviada. Puedes leer el tipo que obtuvo un mint de la factory con `wrapper_kind_by_mint(mint)`; en la factory Hadrian activa, los wrappers de USDC, ETH y SOL devuelven todos `1`.

Qué se rechaza:

* Un mint con un transfer hook armado no puede obtener uno de los wrappers ordinarios. Desplegar `SPL_ERC20` o `SPL_ERC20_cached` directamente sobre él revierte con `ArmedTransferHookUnsupported(mint, hookProgram)`. A través de la factory, este caso va al wrapper con hook en su lugar.
* Una dirección que no es un mint SPL hace que `mint_info` revise con `Cuenta de mint SPL inválida: <address>`.
* Un mint que ya tiene un wrapper revierte con `Token existe`, y un símbolo que ya está en uso revierte con `Token con símbolo existente`.
* `add_spl_token_with_metadata` lee primero el nombre y el símbolo de los propios metadatos Token-2022 del mint y después de Metaplex. Si el mint no tiene ninguno de los dos revierte con `El mint afirma no tener nombre: usa add_spl_token_no_metadata`.

Cómo se comporta el wrapper con hook:

* Token-2022 ejecuta el hook en cada transferencia, y el hook necesita cuentas que dependen de la transferencia. El wrapper no puede deducirlas, así que el llamante resuelve la lista completa de cuentas a partir de la lista de metadatos adicionales de la cuenta del mint y la pasa. El wrapper comprueba la parte fija de esa lista (el programa hook y su cuenta de validación) y la reenvía sin cambios.
* El simple `transfer`, `transferFrom` y `bridgeOutToSolana` revise con `HookAccountPlanRequired`. Usa `transferWithHookAccounts`, `transferFromWithHookAccounts` y `bridgeOutToSolanaWithHookAccounts`, que toman la lista de cuentas como argumento adicional.
* Si el programa hook del mint cambia después de que el wrapper fue desplegado, las transferencias revierten con `HookConfigurationChanged`.
* Mueve tokens mediante una llamada directa entre programas, la ruta heredada, y no usa la ruta en caché. Un contrato usa una ruta de forma consistente, y las llamadas en la ruta heredada se ejecutan solo en ejecución atómica, así que una llamada que use el wrapper con hook tiene que caber en una única transacción atómica.
* No mantiene ningún libro mayor para los saldos de contrato. Los tokens enviados a un contrato llegan a la propia cuenta de token del contrato, y el contrato solo puede enviarlos una vez que haya hecho la `approve_spl` concesión al propio wrapper.
* Necesita la misma [concesión única](#one-time-grant-per-wallet) que cualquier otro wrapper.

## Restricciones

* Las cantidades de tokens SPL son `uint64` (máx. 18,446,744,073,709,551,615)
* Las asignaciones ERC-20 (`approve` / `allowance`) son almacenamiento EVM. El wrapper mueve tokens como delegado SPL del usuario, que el usuario concede una vez por wrapper (ver [Concesión única por wallet](#one-time-grant-per-wallet))
* Los símbolos de wrapper ERC-20 deben ser únicos por factory

## Qué sigue

* [Contract Addresses](/es/referencia/contract-addresses.md) — precompiles y direcciones por cadena
* [Llamar a Solana desde EVM](/es/guias-para-desarrolladores/call-solana-from-evm.md) — CPI y operaciones SPL desde Solidity


---

# 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/es/conceptos-basicos/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.
